-
-
Save ChrisMcKee/6664438 to your computer and use it in GitHub Desktop.
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; | |
} | |
} | |
} | |
} |
into a try, and put the context restoration in the finally, the issue
Do you have an example of how that would work @ssteiner? I'm also experiencing deadlocks during AsyncHelper execution.
For those coming from Google, just use this helper class from the official Microsoft Identity repo to call async methods synchronously:
// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNet.Identity
{
internal static class AsyncHelper
{
private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
_myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
}
}
Thanks, @GFoley83!
For those coming from Google, just use this helper class from the official Microsoft Identity repo to call async methods synchronously:
// Copyright (c) Microsoft Corporation, Inc. All rights reserved. // Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNet.Identity { internal static class AsyncHelper { private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default); public static TResult RunSync<TResult>(Func<Task<TResult>> func) { var cultureUi = CultureInfo.CurrentUICulture; var culture = CultureInfo.CurrentCulture; return _myTaskFactory.StartNew(() => { Thread.CurrentThread.CurrentCulture = culture; Thread.CurrentThread.CurrentUICulture = cultureUi; return func(); }).Unwrap().GetAwaiter().GetResult(); } public static void RunSync(Func<Task> func) { var cultureUi = CultureInfo.CurrentUICulture; var culture = CultureInfo.CurrentCulture; _myTaskFactory.StartNew(() => { Thread.CurrentThread.CurrentCulture = culture; Thread.CurrentThread.CurrentUICulture = cultureUi; return func(); }).Unwrap().GetAwaiter().GetResult(); } } }
Added that ref to the top of this; also this is the first ever notification I've had from a gist... nice of github to finally add that as I hadn't seen a single message here 😆
While the internal asp.net solution from Microsoft may work fine on the asp.net core side of things I'm getting deadlocks when using it in a WPF client while the code given here is running without problems! So be sure to test it; the code is not equivalent! @ChrisMcKee
PS: Using the ExclusiveSynchronizationContext
version given here for the client and the "Microsoft version" for the asp.net core server side seems to work; BUT the seemingly simpler Microsoft version is running slower over 20% slower in my test case on my machine.
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.
How does
.RunSync<T>
differs from just using.Result
?