Search code examples
c#wpfmultithreadingasynchronousmainwindow

WPF MainWindow freezes even in Asynchronous state


What I'm trying to do is perform a heavy task triggered by a button event on the MainWindow, but still be able to drag the window freely. I've tried both the async/await pattern and creating new threads. However, threads will be nonblocking, MainWindow still freezes. Here's the code:

uiTIN.Click += async (o, e) =>
{
    var _ = await Task.Run(() => job());
};

That's the button callback and here is the func:

    private int job()
    {
        this.Dispatcher.Invoke(() =>
        { 
         //Other function calls here omitted
        });
     return 0;
    }

EDIT: The workaround was to use BackgroundWorker and I have also decorated dependent UI code snippets in Dispatcher Invoke function


Solution

  • From Microsoft's doccumentation on Dispatcher (emphasis mine):

    In WPF, a DispatcherObject can only be accessed by the Dispatcher it is associated with. For example, a background thread cannot update the contents of a Button that is associated with the Dispatcher on the UI thread. In order for the background thread to access the Content property of the Button, the background thread must delegate the work to the Dispatcher associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke. Invoke is synchronous and BeginInvoke is asynchronous. The operation is added to the queue of the Dispatcher at the specified DispatcherPriority.

    So basically what you're doing is call an asynchronous method, and then forcing it to run on the UI thread, which accomplishes nothing.

    In your //Other function calls here omitted, I'm asuming that you need to access some part of the UI, if that's not the case, all you have to do is remove the Dispatcher.Invoke from your method.

    If my assumptions are right, then you must figure out a way of splitting your function, so that the part that isn't UI related run in a Background thread and only what needs to run on the UI Thread actually do.

    My suggestion is to use a Background Worker. Here's how it'd look:

    uiTIN.Click += (o, e) =>
    {
        job();
    };
    

    ... and then ...

    private int job()
    {
        BackgroundWorker worker = new BackgroundWorker();
    
        worker.DoWork += (s, e) =>
        {
            // Part of other function calls here omitted that don't need to run on the UI thread
    
            Dispatcher.Invoke(() =>
            {
                // Part of other function calls here omitted that must run on the UI thread
            });
        };
    
        worker.RunWorkerAsync();
    
        return 0;
    }