Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call async over sync c# without thread locking

Tags:

c#

async-await

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?

like image 222
PEtter Avatar asked Aug 12 '26 02:08

PEtter


1 Answers

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.

like image 96
GTHvidsten Avatar answered Aug 14 '26 15:08

GTHvidsten



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!