-1

In my application, I have a task which runs in the background to perform a long-running job. The user can cancel the job anytime by clicking Stop button. The implementation is as belows:

private CancellationTokenSource m_tokenSource;


private void onStop()
{
    m_tokenSource.Cancel();
}

private void onStart()
{
    m_tokenSource = new CancellationTokenSource();
    AsyncDoingJob();
}

private async void AsyncDoingJob()
{
     await Task.Run(() =>
     {
          for (int imgIdx = 0; imgIdx < m_ListImageFiles.Count; imgIdx++)
          {
                if (m_tokenSource.IsCancellationRequested) // Check if the user clicks Stop button
                {
                    return;
                }

                byte[] imageData = File.ReadAllBytes(m_ListImageFiles[imgIdx]); // Load image in background

                Application.Current.Dispatcher.Invoke(() =>
                {
                        m_model.Image = CreateImage(imageData);
                        m_model.Image.Freeze();                      
                        MyImage = m_model.Image;   // This calls NotifyPropertyChanged() to update the image on GUI                     
                }

                // Access to m_model.Image for some reason

          }
     }, m_tokenSource.Token);
}

The problem: Sometimes, while the job is being performed, GUI stop updating (hanging). And if I try to manipulate the GUI, an exception happens: System.Runtime.InteropServices.COMException: UCEERR_RENDERTHREADFAILURE (Exception from HRESULT: 0x88980406)

I found a similar problem reported here: WPF render thread failures

Do you know what it is and how to fix this? Is there anything wrong with my source code?

  • 1
    As a note, `var newImage = CreateImage(imageData); newImage.Freeze();` should not be executed in the Dispatcher Action. The concept of freezing a Freezable is meant to make it cross-thread accessible. You may also entirely avoid the Dispatcher call by moving `Task.Run` into the loop to run the code in the loop's body, except `MyImage = newImage;`. Finally, you should avoid async void methods. Declare AsyncDoingJob async (as the name implies), and await it. – Clemens Oct 25 '19 at 06:31
  • @Clemens If I move Task.Run into the loop, it means almost all the job is performed in UI Thread. So it may not response to user operation e.g. click Stop button. Is that right? – Huyết Công Tử Oct 25 '19 at 06:48
  • @Clemens the part: newImage.Freeze() is needed because after calling Application.Current.Dispatcher.Invoke(), I still need to access to the new BitmapSource for some reason. I have updated the post. – Huyết Công Tử Oct 25 '19 at 07:05
  • 1
    For you first comment, that is not true. Let me provide an alternative implementation. Just a minute. – Clemens Oct 25 '19 at 07:17

2 Answers2

1

I can't tell why you get that error. You may however try an implementation like this:

private bool continueLoading;

private void OnStop()
{
    continueLoading = false;
}

private async Task OnStart()
{
    continueLoading = true;
    await LoadImagesAsync(m_ListImageFiles);
}

private async Task LoadImagesAsync(IEnumerable<string> imageFiles)
{
    foreach (var imageFile in imageFiles)
    {
        if (!continueLoading)
        {
            break;
        }

        Image = await LoadImageAsync(imageFile); // assignment in UI thread

        // do more async calls here if necessary
    }
}

private static Task<ImageSource> LoadImageAsync(string fileName)
{
    return Task.Run(() =>
    {
        using (var stream = File.OpenRead(fileName))
        {
            return LoadImage(stream);
        }
    });
}

private static ImageSource LoadImage(Stream stream)
{
    return BitmapFrame.Create(
        stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);

    // Alternative:
    //
    // var bitmapImage = new BitmapImage();
    // bitmapImage.BeginInit();
    // bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    // bitmapImage.StreamSource = stream;
    // bitmapImage.EndInit();
    // bitmapImage.Freeze();
    // return bitmapImage;
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
0

After migrating a large WPF application from the .NET Framework to .NET 7, for some users the application began to crash with this exception.

System.Runtime.InteropServices.COMException UCEERR_RENDERTHREADFAILURE (0x88980406)

The reason for the exception is not clear, but disabling hardware graphics acceleration worked for me.

using System.Windows.Media;

RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;

P.S. See the document from Microsoft for a discussion of this exception.

Stepagrus
  • 1,189
  • 1
  • 12
  • 19