在不使用线程锁的情况下调用C#中的异步程序。

3

我为内部使用制作了一种轻量级的中介者库。

一个查询可以映射到一个异步方法,而调用方不需要知道任何信息。

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);
    }

当调用者在同步线程中,例如服务容器中的工厂方法并希望执行查询时,它不能使用ExecuteQueryAsync异步执行查询,但查询的实际处理程序,即实现了IAsyncQueryHandler接口的处理程序,是异步的。
所以我需要在同步代码中调用异步代码。
我在注册时使用反射来确定如何调用处理程序,我尝试过使用.wait()和.result()但会出现线程锁死。
当前的代码如下。
private object AsyncSync(object service, object param)
{
  dynamic t = Method.Invoke(service, new[] { param });
  return t.GetAwaiter().GetResult();
}
Method 是反射中的方法信息,service 是该方法所在类的实例。

这段代码仍然导致线程死锁。

我该如何实现它?我需要查看任务工厂吗?有什么建议吗?


5
注意:这不是“异步在同步上面”,而是“同步在异步上面”——唯一有效的答案是:“不要这样做”;如果你想使用一个可等待对象:那就“自己成为异步”;没有任何变通方法可以让这样做变得合理。 - Marc Gravell
如果你需要同步API,请提供一个真正的API。即使这意味着以两种不同的方式完成相同的事情,但这样做可能会更快速和更稳定。 - Fildor
你提到需要在“服务容器中的工厂方法”内调用异步方法。这是绝对不可取的,因为对象解析应该快速可靠(请阅读此文此文)。异步调用是缓慢的,涉及I/O操作,因此不可靠。 - Steven
是的,我同意这一点,也许可以想出一种实现方法。但在这种特殊情况下,我需要从数据库中获取一些数据。在请求开始时。但除此之外,我同意:D - PEtter
2个回答

3

正如已经评论过的那样,最佳解决方案是一路使用 async... 但有时这是不可能的。即使微软也承认了这一点,通过在其框架中提供工具来同步运行 async 方法:

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

使用方法如下:

public async Task<int> DoSomethingAsync()
{
    await DoSomethingElseAsync();
    return 0;
}

int result = AsyncHelper.RunSync(async () => await DoSomethingAsync());

我对此的感觉是,如果微软在他们自己的框架中这样做,那么我们也可以这样做。


愚蠢的问题。使用AsyncHelper.RunSync(() => DoSomethingAsync());有什么问题吗?(在RunSync()中没有asyncawait关键字) - Ngoc Pham

0

非常感谢GTHvidsten。 这似乎是有效的解决方案。

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 }));
}

我不喜欢动态的东西,但这是一个简单的方法 :)


仅仅因为它工作良好,并不意味着您应该抛弃类型信息并添加运行时开销。您的中介者只需要 IQueryHandler 的实现,该实现调用任何 IAsyncQueryHandlerRunSync<TResult>(反之亦然)。不应该需要反射。 - Jeremy Lakeman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接