简体   繁体   English

同步执行异步函数

[英]Executing async function synchronously

I have done a lot of search on this topic, and I read most of the posts here on this site regarding this topic, however I'm still confused and I need a straight forward answer.我已经对这个主题进行了大量搜索,并且我阅读了本网站上关于这个主题的大部分帖子,但是我仍然感到困惑,我需要一个直接的答案。 Here is my situation:这是我的情况:

I have an established Winform application that I can't make it all 'async'.我有一个已建立的 Winform 应用程序,我不能让它全部“异步”。 I'm forced now to use an external library that is all written as async functions.我现在被迫使用一个全部编写为异步函数的外部库。

In my application I have在我的应用程序中,我有

/// <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
    }
}

As outlined in the code, I need to be able to:如代码中所述,我需要能够:

  • I can't change my MySyncFunction to an async我无法将 MySyncFunction 更改为异步
  • Call the "MyAsyncDriverFunction" in a sync way, where it have to wait for it to finish all its work before I process the code that follows以同步方式调用“MyAsyncDriverFunction”,在我处理后面的代码之前,它必须等待它完成所有工作
  • Be able to handle exceptions in both functions (from what I read so far this is tricky?)能够处理两个函数中的异常(从我目前阅读的内容来看,这很棘手?)
  • I need a simple way using the standard API, I can't use any third party library (even if I wanted to)我需要一种使用标准 API 的简单方法,我不能使用任何第三方库(即使我想使用)

however I'm still confused and I need a straight forward answer.但是我仍然很困惑,我需要一个直接的答案。

That's because there isn't a "straight-forward" answer .那是因为没有“直截了当”的答案

The only proper solution is to make MySyncFunction asynchronous.唯一合适的解决方案是使MySyncFunction异步。 Period.时期。 All other solutions are hacks, and there is no hack that works perfectly in all scenarios .所有其他解决方案都是 hack,并且没有在所有场景中都能完美运行的 hack

I go into full details in my recent MSDN article on brownfield async development , but here's the gist:我在最近的 MSDN 文章中详细介绍了棕地异步开发,但要点如下:

You can block with Wait() or Result .您可以使用Wait()Result进行阻塞。 As others have noted, you can easily cause a deadlock, but this can work if the asynchronous code never resumes on its captured context.正如其他人所指出的,您很容易导致死锁,但如果异步代码永远不会在其捕获的上下文中恢复,则这可以起作用。

You can push the work to a thread pool thread and then block.您可以将工作推送到线程池线程,然后阻塞。 However, this assumes that the asynchronous work is capable of being pushed to some other arbitrary thread and that it can resume on other threads, thus possibly introducing multithreading.然而,这假设异步工作能够被推送到某个其他任意线程并且它可以在其他线程上恢复,因此可能会引入多线程。

You can push the work to a thread pool thread that executes a "main loop" - eg, a dispatcher or my own AsyncContext type.您可以将工作推送到执行“主循环”的线程池线程 - 例如,调度程序或我自己的AsyncContext类型。 This assumes the asynchronous work is capable of being pushed to another thread but removes any concerns about multithreading.这假设异步工作能够被推送到另一个线程,但消除了对多线程的任何担忧。

You can install a nested message loop on the main thread.您可以在主线程上安装嵌套消息循环。 This will execute the asynchronous code on the calling thread, but also introduces reentrancy, which is extremely difficult to reason about correctly.这将在调用线程上执行异步代码,但也会引入可重入性,这是非常难以正确推理的。

In short, there is no one answer .简而言之,没有一个答案 Every single approach is a hack that works for different kinds of asynchronous code.每一种方法都是一种适用于不同类型异步代码的技巧。

Simply calling .Result or .Wait against your async method will deadlock because you're in the context of a GUI application.简单地针对异步方法调用.Result.Wait将会死锁,因为您处于 GUI 应用程序的上下文中。 See https://msdn.microsoft.com/en-us/magazine/jj991977.aspx (chapter 'Async All the Way') for a nice explanation.请参阅https://msdn.microsoft.com/en-us/magazine/jj991977.aspx (“一路异步”一章)以获得很好的解释。

The solution to your problem is not easy, but it has been described in details by Stephen Cleary: here .解决您的问题并不容易,但 Stephen Cleary 已对其进行了详细描述:这里

So you should use the Nito.AsyncEx library (available on Nuget ).所以你应该使用 Nito.AsyncEx 库(在Nuget可用)。 If you really can't add the library he wrote to your project, you could check the source code and use portions of it, the MIT license allows it.如果您真的无法将他编写的库添加到您的项目中,您可以检查源代码并使用其中的一部分,MIT 许可证允许这样做。

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

var strCollection = MyAsyncDriverFunction().Result;

I'm not sure what the experts would say, but based on the Stephen Cleary advices I end up with the following idea.我不确定专家会说什么,但根据Stephen Cleary 的建议,我最终得出了以下想法。 Having the following class有以下课程

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

and use it as follows并按如下方式使用它

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

would do what you are asking for without deadlock.会在没有僵局的情况下做你所要求的。 The key point is to "hide" the current synchronization context during the asynchronous task execution and force using of the default synchronization context which is known to use thread pool for Post method.关键是在异步任务执行期间“隐藏”当前同步上下文,并强制使用默认同步上下文,该上下文已知将线程池用于Post方法。 Again, I'm not sure if this is good or bad idea and what side effects it could introduce, but once you asked, I'm just sharing it.同样,我不确定这是好主意还是坏主意,也不确定它会带来什么副作用,但是一旦你问了,我只是分享它。

尝试将“await AsyncExternalLibraryFunction1()”更改为“AsyncExternalLibraryFunction1().Wait()”并在其旁边,并删除函数“MyAsyncDriverFunction”的异步

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM