同步执行async函数

本文关键字:函数 async 执行 同步 | 更新日期: 2023-09-27 18:17:13

我已经做了很多关于这个主题的搜索,我读了这个网站上关于这个主题的大多数帖子,但是我仍然很困惑,我需要一个直接的答案。以下是我的情况:

我有一个已建立的Winform应用程序,我不能使它全部'异步'。我现在被迫使用一个外部库,它都是作为异步函数编写的。

在我的应用程序中我有

/// <summary>
/// This function I can't change it to an 'async'
/// </summary>
public void MySyncFunction()
{
    //This function is my point in my application where I have to call the
    //other 'async' functions but I can't change the function itself to 'async'
    try
    {
        //I need to call the MyAsyncDriverFunction() as if it is a synchronous function
        //I need the driver function to finish execution and return before processing the code that follows it
        //I also need to be able to catch any exceptions
        MyAsyncDriverFunction(); 
        //Rest of the code have to wait for the above function to return
    }
    catch (Exception exp)
    {
        //Need to be able to handle the exception thrown 
        //from the MyAsyncDriverFunction here.  
    }
}
public static async Task<IEnumerable<string>> MyAsyncDriverFunction()
{
    try
    {
        var strCollection = await AsyncExternalLibraryFunction1();
        var strCollection2 = await AsyncExternalLibraryFunction2();
        return strCollection;
    }
    catch (Exception exp)
    {
        //Need to be able to catch an exception and re-throw it to the caller function
    }
}

正如代码中概述的那样,我需要能够:

  • 我不能改变我的MySyncFunction为异步
  • 调用"MyAsyncDriverFunction"在同步的方式,它必须等待它完成所有的工作之前,我处理以下的代码
  • 能够处理两个函数中的异常(从我读到目前为止这是棘手的吗?)
  • 我需要一个使用标准API的简单方法,我不能使用任何第三方库(即使我想)

同步执行async函数

但是我还是很困惑,我需要一个直截了当的答案。

这是因为没有一个"直接"的答案

唯一合理的解决方案是使MySyncFunction异步。时期。所有其他的解决方案都是hack,而没有一个hack在所有情况下都能完美地工作

我在最近的MSDN文章中详细介绍了棕地异步开发,但这里是要点:

可以用Wait()Result阻断。正如其他人注意到的那样,您很容易导致死锁,但如果异步代码永远不会在其捕获的上下文中恢复,则这可以工作。

可以将工作推送到线程池线程,然后阻塞。然而,这假设异步工作能够被推送到其他任意线程,并且可以在其他线程上继续,因此可能引入多线程。

你可以将工作推送到执行"主循环"的线程池线程中——例如,调度程序或我自己的AsyncContext类型。这假设异步工作能够被推送到另一个线程,但消除了对多线程的任何担忧。

可以在主线程上安装嵌套的消息循环。这将在调用线程上执行异步代码,但也引入了重入性,这是极其难以正确推理的。

总之,没有唯一的答案。每一种方法都是一种hack,适用于不同类型的异步代码

简单地对async方法调用.Result.Wait将会死锁,因为您处于GUI应用程序的上下文中。请参阅https://msdn.microsoft.com/en-us/magazine/jj991977.aspx(章节'Async All Way')获得一个很好的解释。

解决你的问题并不容易,但Stephen Cleary已经详细描述了:在这里。

所以你应该使用Nito。AsyncEx库(在Nuget上可用)。如果您确实不能将他编写的库添加到您的项目中,您可以检查源代码并使用其中的一部分,MIT许可允许这样做。

只需在方法调用的末尾添加一个.Result调用。

var strCollection = MyAsyncDriverFunction().Result;

我不知道专家们会怎么说,但基于Stephen Cleary的建议,我最终有了以下想法。具有以下类

public sealed class AsyncTask
{
    public static void Run(Func<Task> asyncFunc)
    {
        var originalContext = SynchronizationContext.Current;
        bool restoreContext = false;
        try
        {
            if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
            {
                restoreContext = true;
                SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            }
            var task = asyncFunc();
            task.GetAwaiter().GetResult();
        }
        finally
        {
            if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
        }
    }
    public static TResult Run<TResult>(Func<Task<TResult>> asyncFunc)
    {
        var originalContext = SynchronizationContext.Current;
        bool restoreContext = false;
        try
        {
            if (originalContext != null && originalContext.GetType() != typeof(SynchronizationContext))
            {
                restoreContext = true;
                SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
            }
            var task = asyncFunc();
            return task.GetAwaiter().GetResult();
        }
        finally
        {
            if (restoreContext) SynchronizationContext.SetSynchronizationContext(originalContext);
        }
    }
}

,并按如下方式使用

public void MySyncFunction()
{
    try
    {
        AsyncTask.Run(() => MyAsyncDriverFunction()); 
    }
    catch (Exception exp)
    {
    }
}

将在没有死锁的情况下执行您所要求的操作。关键是在异步任务执行期间"隐藏"当前同步上下文,并强制使用默认同步上下文,该上下文已知为Post方法使用线程池。再说一次,我不确定这是好是坏,也不知道它会带来什么副作用,但一旦你问了,我就和你分享。

尝试将"await AsyncExternalLibraryFunction1()"更改为"AsyncExternalLibraryFunction1().Wait()"并在其旁边删除