Skip to content

Instantly share code, notes, and snippets.

@ChrisMcKee
Last active December 6, 2023 11:37
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ChrisMcKee/6664438 to your computer and use it in GitHub Desktop.
Save ChrisMcKee/6664438 to your computer and use it in GitHub Desktop.
AsyncHelpers to simplify calling of Async/Task methods from synchronous context. (use https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs this is ancient code)
namespace My.Common
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public static class AsyncHelpers
{
/// <summary>
/// Execute's an async Task<T> method which has a void return value synchronously
/// </summary>
/// <param name="task">
/// Task<T> method to execute
/// </param>
public static void RunSync(Func<Task> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
synch.Post(async _ =>
{
try
{
await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
}
/// <summary>
/// Execute's an async Task<T> method which has a T return type synchronously
/// </summary>
/// <typeparam name="T">Return Type</typeparam>
/// <param name="task">
/// Task<T> method to execute
/// </param>
/// <returns></returns>
public static T RunSync<T>(Func<Task<T>> task)
{
var oldContext = SynchronizationContext.Current;
var synch = new ExclusiveSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(synch);
T ret = default(T);
synch.Post(async _ =>
{
try
{
ret = await task();
}
catch (Exception e)
{
synch.InnerException = e;
throw;
}
finally
{
synch.EndMessageLoop();
}
}, null);
synch.BeginMessageLoop();
SynchronizationContext.SetSynchronizationContext(oldContext);
return ret;
}
private class ExclusiveSynchronizationContext : SynchronizationContext
{
private readonly Queue<Tuple<SendOrPostCallback, object>> _items =
new Queue<Tuple<SendOrPostCallback, object>>();
private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false);
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<SendOrPostCallback, object> 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;
}
}
}
}
@molekp
Copy link

molekp commented Feb 24, 2022

You should also notice that Microsoft version will not throw exception from executed task. It just will run it and wait for end. Your version will rethrow the exception from the task.

You should consider it when choosing version of AsyncHelper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment