WPF calling asynchronous code with out locking on Task..WhenAny()

390 views Asked by At

I have been trying to setup a very basic method in my view models for temporarily freezing the UI thread while still pumping dispatcher messages, I have read this method will only work for simple cases and is not thread safe as 'waiting' for a task while already 'waiting' for a task causes buggy-ness.

But here is the method I wrote to do this so far:

        public T WaitForTask<T>(Task<T> task, bool throwException = false)
        {
            WaitForTask((Task)task, throwException);
            return (!task.IsCompleted || task.IsFaulted || task.Exception != null) ? default(T) : task.Result;
        }

        public void WaitForTask(Task task, bool throwException = false)
        {
            if (task == null)
                throw new ArgumentNullException("task");

            // Wait for the task to complete
            IsWaiting = true;
            using (MouseWait.Begin())
            {
                var runTask = UIDispatcher.InvokeAsync(async () =>
                    {
                        await task;
                    });

                // Wait for the taske to finish
                while (!task.IsCompleted)
                {
                    DispatcherPush();
                    Thread.Sleep(10);
                }

                var exception = task.Exception;
                if (exception != null)
                {
                    if(exception is AggregateException)
                    {
                        var aggregateException = (AggregateException)exception;
                        log.ErrorFormat("{0}.WaitForTask(): {1}", GetType().Name, ExceptionUtility.JoinAggregateExceptionMessages(aggregateException));
                    }
                    else
                    {
                        var exceptionMessage = ExceptionUtility.JoinExceptionMessages(exception);
                        log.ErrorFormat("{0}.WaitForTask(): {1}", GetType().Name, exceptionMessage);
                    }

                    if (throwException)
                        throw exception;
                }
            }
            IsWaiting = false;
        }

This works fine for waiting for a single async task at a time which is all I need in my current applications scope, however when I began to optimize my async I found a number of places where using the Task.WhenAny() method would be suitable. However calling Task.WhenAny() causes the application to lock...

I think my issue might be related to the answer in this thread: C#/.NET 4.5 - Why does "await Task.WhenAny" never return when provided with a Task.Delay in a WPF application's UI thread?

Any suggestions would be appreciated.

Thanks, Alex.

1

There are 1 answers

0
NeddySpaghetti On BEST ANSWER

You can achieve that with AsyncBridge. E.g.

using (var asyncBridge = AsyncHelper.Wait)
{
    asyncBridge.Run(DoWorkAsync());
}

It works by installing a custom SynchronizationContext and using that to wait for the task whilst freeing the UI thread.

Because it is using synchronization context rather than using the dispatcher directly it is essentially framework agnostic and will work for both WPF and Winforms, which is why the SynchronizatonContext concept was introduced. It basically abstracts how work is given to various threads.