将同步代码包装成异步方法的最佳方法是啥

Posted

技术标签:

【中文标题】将同步代码包装成异步方法的最佳方法是啥【英文标题】:What is the best way for wrapping synchronous code into asynchronous method将同步代码包装成异步方法的最佳方法是什么 【发布时间】:2015-08-09 18:22:42 【问题描述】:

我正在使用 async-await 方法创建一个应用程序。但是使用它们对我来说存在很大的问题。在阅读了几篇文章后,我仍然不知道将繁重的同步操作包装到异步方法的最佳方法是什么。

我有两个想法。哪个最好?

1) 当前实现。

private Task<List<UploadedTestModel>> ParseTestFiles(List<string> filesContent)

    var tcs = new TaskCompletionSource<List<UploadedTestModel>>();
    Task.Run(() =>
    
        var resultList = new List<UploadedTestModel>();
        foreach (var testBody in filesContent)
        
            try
            
                var currentCulture = Thread.CurrentThread.CurrentCulture;
                var serializerSettings = new JsonSerializerSettings
                
                    Culture = currentCulture
                ;

                var parsedData = JsonConvert.DeserializeObject<UploadedTestModel>(testBody, serializerSettings);
                resultList.Add(parsedData);
            
            catch(Exception exception)
            
                tcs.SetException(exception);
            
        
        tcs.SetResult(resultList);
    );
    return tcs.Task;

我正在使用 Task.Run 和 TaskCompletionSource

2) 仅使用没有 TaskCompletionSource 的 Task.Run

private Task<List<UploadedTestModel>> ParseTestFiles(List<string> filesContent)

    return Task.Run(() =>
    
       . . . .
       return resultList;          
    );

【问题讨论】:

Task.Run 大约是您将获得的最好的 - 您不能通过仅以特殊方式调用它来使固有的同步操作真正异步。如果操作是 CPU 密集型的(或者只是包装了一些阻塞线程的东西),它需要在线程上运行。 据我所知,foreach 的主体本身可以以异步方式运行(resultList.Add 除外)。即使在那之后,我也不确定JsonConvert.DeserializeObject 是否真的从并行任务中受益。也许您应该使用信号量将并行计算限制在特定数量 - 比如 CPU 的数量?再一次,我不确定这个数字——你是在 x64 还是 x86 上,我不确定你可以从 .NET 的线程池机制中受益多少——这是底层的Task runner(调度程序)。 & 老实说,我只是盲目地信任 Tasks 的 .NET 底层机制,我做得很好。但是对于更繁重的事情,您最好使用 Akka.NET 之类的东西。 @Egorikas 请记住 asyncawait 在 UI 应用程序中的行为有点不同 - 它们保留上下文。所以你必须使用明确的Task 启动;然后awaitTask @KavehShahbazian 我明白了。谢谢你的解释 【参考方案1】:

我都不会使用。你会对调用你的方法调用的人撒谎。当你公开一个异步操作时,调用者希望它自然是异步的,这意味着它背后没有线程在工作。

你所有的方法都是天生同步的,你应该这样公开它们。让调用者决定是要同步调用它们还是使用线程并在那里排队,不要为它们决定。

Stephan Toub 写了一篇名为Should I expose asynchronous wrappers for synchronous methods? 的精彩文章,其中谈到了不做你想做的事的所有原因。我建议阅读它。

【讨论】:

Lucian Wischik 有一个标题为 Async Library Methods Shouldn't Lie 的视频,它说了同样的话。 @Yuval 我读过这篇文章。之后,我开始思考我的代码。那么,你能解释一下吗?您是否提供使此方法同步,然后使用任务调用它(它阻塞了我的 UI 线程,所以我需要用它做点什么)。 在 wpf、windows 窗体、uwp 等应用程序上使用同步操作是否有意义?您阻塞了 UI 线程,它会冻结应用程序。在这种情况下,包装为 asnyc 不是更好吗? @batmaci 确实,阻塞 UI 线程是没有意义的。但是我仍然不会将方法呈现为异步的,我只是从事件处理程序中调用Task.Run

以上是关于将同步代码包装成异步方法的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

使用async和await将同步方法包装成异步方法

计算用于运输的最佳包装箱尺寸的最佳方法是啥? [复制]

将同步 API 包装到 Async 方法中

将 rsync 进度包装在 gui 中的最佳方法?

将非异步方法(进行网络调用)包装到异步中

在 C# 中为同步/异步任务添加重试/回滚机制的最佳方法是啥?