HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpCritical

How would I run an async Task<T> method synchronously?

Submitted by: @import:stackoverflow-api··
0
Viewed 0 times
runhowasyncmethodwouldsynchronouslytask

Problem

I am learning about async/await, and ran into a situation where I need to call an async method synchronously. How can I do that?

Async method:

public async Task GetCustomers()
{
    return await Service.GetCustomersAsync();
}


Normal usage:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}


I've tried using the following:

Task task = GetCustomers();
task.Wait()

Task task = GetCustomers();
task.RunSynchronously();

Task task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)


I also tried a suggestion from here, however it doesn't work when the dispatcher is in a suspended state.

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}


Here is the exception and stack trace from calling RunSynchronously:

System.InvalidOperationException

Message: RunSynchronously may not be called on a task unbound to a delegate.

InnerException: null

Source: mscorlib

StackTrace:

``
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Task.RunSynchronously()
at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
at MyApplication.CustomControls.Controls.MyCustomControl.b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
at System.Collections.Generic.List
1.ForEach(Action`1 action)
at MyApplication.Custom

Solution

Here's a workaround I found that works for all cases (including suspended dispatchers). It's not my code and I'm still working to fully understand it, but it does work.

It can be called using:

customerList = AsyncHelpers.RunSync>(() => GetCustomers());

Code is from here

public static class AsyncHelpers
{
    /// 
    /// Synchronously execute's an async Task method which has a void return value.
    /// 
    /// The Task method to execute.
    public static void RunSync(Func task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        
        syncContext.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// 
    /// Synchronously execute's an async Task method which has a T return type.
    /// 
    /// Return Type
    /// The Task method to execute.
    /// The result of awaiting the given Task.
    public static T RunSync(Func> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        T result;
        
        syncContext.Post(async _ =>
        {
            try
            {
                result = await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();
        
        SynchronizationContext.SetSynchronizationContext(oldContext);
        
        return result;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        private readonly Queue> items =
            new Queue>();
        private bool done;
        
        public Exception InnerException { get; set; }

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

Code Snippets

public static class AsyncHelpers
{
    /// <summary>
    /// Synchronously execute's an async Task method which has a void return value.
    /// </summary>
    /// <param name="task">The Task method to execute.</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        
        syncContext.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Synchronously execute's an async Task<T> method which has a T return type.
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">The Task<T> method to execute.</param>
    /// <returns>The result of awaiting the given Task<T>.</returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var syncContext = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(syncContext);
        T result;
        
        syncContext.Post(async _ =>
        {
            try
            {
                result = await task();
            }
            catch (Exception e)
            {
                syncContext.InnerException = e;
                throw;
            }
            finally
            {
                syncContext.EndMessageLoop();
            }
        }, null);
        
        syncContext.BeginMessageLoop();
        
        SynchronizationContext.SetSynchronizationContext(oldContext);
        
        return result;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        private readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();
        private bool done;
        
        public Exception InnerException { get; set; }

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

Context

Stack Overflow Q#5095183, score: 548

Revisions (0)

No revisions yet.