Reputation: 836
I have made a lightweight mediator kind of library for use internally.
A query can be mapped to a async method without the caller knowing anything about it.
public interface IMediator
{
TResult ExecuteQuery<TResult>(Query<TResult> query);
Task<TResult> ExecuteQueryAsync<TResult>(Query<TResult> query);
Task ExecuteCommandAsync(Command command);
Task<TResult> ExecuteCommandAsync<TResult>(Command<TResult> command);
void ExecuteCommand(Command command);
TResult ExecuteCommand<TResult>(Command<TResult> command);
void EnqueueTask(object task);
}
public abstract class Query<TResult> { }
public interface IQueryHandler<Query, TResult> where Query : Query<TResult>
{
TResult Handle(Query param);
}
public interface IAsyncQueryHandler<Query, TResult> where Query : Query<TResult>
{
Task<TResult> Handle(Query param);
}
When the caller is in a sync thread e.g factory method in the service container and want to execute a query it can not do that async using the ExecuteQueryAsync
but the actual handler of the query, implementing the IAsyncQueryHandler
is async.
So I need to call async over sync.
I do some reflection a registration time to actually figure out how to call the handler, I tried the .wait() and .result() but I get thread locks.
current code looks like this.
private object AsyncSync(object service, object param)
{
dynamic t = Method.Invoke(service, new[] { param });
return t.GetAwaiter().GetResult();
}
The Method
is the method info from reflection, and the service
is an instance of the class with that method.
This code still gives me deadlock of threads.
How could I achieve this with, do I need to look into the task factory? Any suggestions?
Upvotes: 3
Views: 1676
Reputation: 836
Big thanks to GTHvidsten This is the solution that seems to be working.
private object RunTaskTSync(Func<Task<object>> task)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _taskFactory
.StartNew(()=> {
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return task(); })
.Unwrap()
.GetAwaiter()
.GetResult();
}
private object AsyncSync(object service, object param)
{
return RunTaskTSync(async () => await (dynamic)Method.Invoke(service, new[] { param }));
}
I do not like the dynamic thing, but it is an easy way to do it :)
Upvotes: 0
Reputation: 4418
As as been commented already, the optimal solution would be to have async
all the way... but sometimes that's not possible. Even Microsoft has acknowledged this by having a tool in their own frameworks to run async
methods synchronously:
public 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();
}
}
https://github.com/aspnet/AspNetIdentity/blob/main/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
You use it like this:
public async Task<int> DoSomethingAsync()
{
await DoSomethingElseAsync();
return 0;
}
int result = AsyncHelper.RunSync(async () => await DoSomethingAsync());
My feeling about this is that if Microsoft does this in their own frameworks, then it's OK for us to do the same.
Upvotes: 3