是不是 .NET 4.0 TPL 让 APM、EAP 和 BackgroundWorker 异步模式过时了?

Posted

技术标签:

【中文标题】是不是 .NET 4.0 TPL 让 APM、EAP 和 BackgroundWorker 异步模式过时了?【英文标题】:Wasn't it .NET 4.0 TPL that made APM, EAP and BackgroundWorker asynchronous patterns obsolete? 【发布时间】:2013-04-28 02:44:09 【问题描述】:

我有 2 种 C# WPF 应用项目:

基于.NET 4.0 我无法迁移到.NET 4.5 基于 .NET 4.0,我可以迁移到 .NET 4.5

所有这些都应该产生 2-10 个长时间运行(天)的进程,用户可以取消并重新启动这些进程。

我有兴趣遵循最佳设计实践。首先,现在,我有兴趣消除 BackgroundWorker 用法的歧义,但我希望我的问题对于其他异步模式应该是有效的。

我看到(矛盾的)关于

的并发观点 Asynchronous Programming Model (APM) Event-based Asynchronous Pattern (EAP) BackgroundWorker

异步模式:

A) .NET 4.5 使它们过时了

在约瑟夫·阿尔巴哈里 (Joseph Albahari) 和本·阿尔巴哈里 (Ben Albahari) 的书中命名为“C# 5.0 in a Nutshell: The Definitive Reference”子章节“Obsolete Patterns”,而其上一版“C# 4.0 in a Nutshell: The Definitive Reference” "没有 MSDN 文章"Asynchronous Programming with Async and Await (C# and Visual Basic)" 讲述:

“基于异步的异步编程方法优于 几乎所有情况下的现有方法。特别是,这种方法 对于 IO 绑定操作,比 BackgroundWorker 更好,因为 代码更简单,您不必防范竞争条件。 结合Task.Run,​​异步编程优于 BackgroundWorker 用于 CPU 绑定操作,因为异步编程 将运行代码的协调细节与工作分开 Task.Run 转移到线程池”

B) 他们(或者,至少,BackgroundWorker) are not obsolete in .NET 4.5

我还是有疑问:

    这些模式(首先是 BGW)在 .NET 4.5 中是否已过时?

    如果它们在 .NET 4.5 中已过时,为什么它们在 .NET 4.0 中不再过时?

    2A) 我是否错误地理解 .NET 4.5 的新功能在 .NET 4.0 中仍然“容易”实现/重现?

【问题讨论】:

【参考方案1】:

如果使用 .NET 4.5,我通常推荐 Task 和/或 await。但是Task & BGW 有两种截然不同的场景。 Task 适用于可以链接到延续的一般短异步任务,而 await 适用于隐式编组回 UI 线程的任务。 BGW 适用于不应影响 UI 响应能力的单个长时间操作。您可以将 BGW 拖放到设计图面上并双击以创建事件处理程序。如果您不想编组到另一个线程,则不必处理LongRunningConfigureAwait。许多人发现 BGW 的进展比 IProgress<T> 更容易。

以下是在“冗长操作”场景中同时使用这两种方法的一些示例:

由于问题特别提到了 .NET 4.0,以下是简单的代码,它使用 Task 执行冗长的操作,同时为 UI 提供进度:

startButton.Enabled = false;
var task = Task.Factory.
                StartNew(() =>
                    
                        foreach (var x in Enumerable.Range(1, 10))
                        
                            var progress = x*10;
                            Thread.Sleep(500); // fake work
                            BeginInvoke((Action) delegate  
                               progressBar1.Value = progress; 
                            );
                        
                    , TaskCreationOptions.LongRunning)
                .ContinueWith(t =>
                    
                        startButton.Enabled = true;
                        progressBar1.Value = 0;
                    );

BackgroundWorker 类似的代码可能是:

startButton.Enabled = false;
BackgroundWorker bgw = new BackgroundWorker  WorkerReportsProgress = true ;
bgw.ProgressChanged += (sender, args) => 
     progressBar1.Value = args.ProgressPercentage; ;
bgw.RunWorkerCompleted += (sender, args) =>

    startButton.Enabled = true;
    progressBar1.Value = 0;
;
bgw.DoWork += (sender, args) =>

    foreach (var x in Enumerable.Range(1, 10))
    
        Thread.Sleep(500);
        ((BackgroundWorker)sender).ReportProgress(x * 10);
    
;
bgw.RunWorkerAsync();

现在,如果您使用的是 .NET 4.5,则可以使用 Progress<T> 而不是使用 Task 调用的 BeginInvoke。从 4.5 开始,使用 await 可能会更易读:

startButton.Enabled = false;
var pr = new Progress<int>();
pr.ProgressChanged += (o, i) => progressBar1.Value = i;
await Task.Factory.
            StartNew(() =>
                        
                            foreach (var x in Enumerable.Range(1, 10))
                            
                                Thread.Sleep(500); // fake work
                                ((IProgress<int>) pr).Report(x*10);
                            
                        , TaskCreationOptions.LongRunning);
startButton.Enabled = true;
progressBar1.Value = 0;

使用Progress&lt;T&gt; 意味着代码不耦合到特定的UI 框架(即调用BeginInvoke),就像BackgroundWorker 促进与特定的UI 框架解耦一样。如果你不在乎,那么你不需要介绍使用Progress&lt;T&gt;增加的复杂性

至于LongRunning,正如 Stephen Toub 所说:“如果您通过性能测试发现不使用 LongRunning 会导致其他工作处理的长时间延迟,您通常只会使用 LongRunning”所以,如果您发现需要使用它,然后你就使用它——有额外的分析或者只是总是添加LongRunning 参数的“复杂性”。不使用 LongRunning 意味着用于长时间运行操作的线程池线程将不能用于其他更临时的任务,并且可能会强制线程池在启动另一个线程时延迟启动其中一个临时任务(至少第二)。

框架中没有属性明确指出 BGW(或 EAP 或 APM)已弃用。因此,由您决定这些东西在何时何地“过时”。特别是 BGW 总是有一个非常具体的使用场景,仍然适用于它。在 .NET 4.0 和 4.5 中有相当不错的选择;但我真的不认为 BGW 是“过时的”。

我并不是说总是使用BackgroundWorker,我只是说在你自动弃用 BackgroundWorker 之前想想,在某些情况下它可能是一个更好的选择。

【讨论】:

不知道为什么所有答案都转移到 .NET 4.5 虽然这个问题是专门针对 .NET 4.0 的,并且出于非常清楚和明显的原因和目的 - 促进和缩短 4.0 中的分析、重构和设计决策相关的重要问题,像What can I do in C# 5 with .Net 4.5 that I couldn't do in C# 4 with .Net 4? 一样被控制和关闭。不过,考虑到 .NET 4.5 无法在 Windows XP 上安装/运行,这并不明显 @Геннадий-Ванин 可能是因为 OP 建议某些东西在 4.5 中被认为已过时(从技术上讲,它们并非如此)。 稍微扩展一下,如果您想做的工作涉及通知更改的事件,您可以关闭 Progress&lt;&gt;variable processor.ItemProcessed += (sender, e1) =&gt; ((IProgress&lt;int&gt;) p).Report(e1.NextItem); 和它 Just works ™ 如何将后台工作人员拖放到设计图面上?我查了一下,它应该在工具箱的组件选项卡中,但该选项卡没有显示。 @KyleDelaney 在 WinForms 应用程序中?我相信BackgroundWorker 仅适用于 WinForm 表单。【参考方案2】:

我认为这些模式(尤其是 APM、EAP 和 BGW)在 .NET 4.5 中已过时。 asyncTask.Run的组合在各方面都优于BGW。事实上,我刚刚开始了series on my blog,我将在其中将 BGW 与Task.Run 进行比较,并展示它在每种情况下如何变得更加麻烦;在某些情况下它只是稍微更麻烦,但在其他情况下它更麻烦。

现在,它们在 .NET 4.0 中是否已过时完全是另一个问题。在您的其他帖子中,您正在谈论为 .NET 4.0 使用 VS2010 进行开发,因此不能选择向后移植 Microsoft.Bcl.Async。在这种情况下,APM 和 EAP 都不能被视为过时的 IMO。在这个平台上,您可以考虑将Task.Factory.StartNew 作为 BGW 的替代品,但 BGW 在进度报告以及其进度和完成事件的自动线程封送方面确实具有一些优势。

更新:我最近更新了我的一篇旧博客文章,我在其中discuss various implementations of background operations。在那篇文章中,当我谈论“任务(异步方法)”时,我的意思是使用 Task 以及所有 .NET 4.5 async 支持、Task.Run 等。“任务(任务并行库)”部分是评估存在于 .NET 4.0 中的 Task

【讨论】:

如果使用 .NET 4.5,我通常推荐 Task 和/或 await。但是 Task & BGW 有 2 个不同的场景。任务适用于可以链接到延续的一般短异步任务,等待是很好的吸吮任务,隐式编组回 UI 线程。 BGW 适用于不应影响 UI 响应能力的单个长时间操作。您可以将 BGW 拖放到设计图面上并双击以创建事件处理程序。如果您不想编组到另一个线程,则不必处理 LongRunning 或 ConfigureAwait。许多人发现 BGW 进度比 IProgress 更容易 @Peter-Ritchie,请将您的评论作为答案。我对长期运行的操作场景感兴趣,但我无法清楚地掌握“过时”公告的解释和基本原理,以及何时、为什么和在哪里。它非常令人困惑,尤其是在新概念的背景下 @ГеннадийВанинНовосибирск: Task.Run 非常适合长时间运行的任务,尤其是在桌面应用程序中。在这种情况下,LongRunningConfigureAwait 都是不必要的。 但是后台工作人员可以从工具箱中拖到设计器上,对吧? asyncTask.Run 是否复制了该功能? @KyleDelaney:没有。asyncTask.Run 期待你写代码。【参考方案3】:

我的问题是:

难道不是 .NET 4.0 TPL 造就了 APM、EAP 和 BackgroundWorker 异步模式过时了吗?

即一个疑问,要求确认或否定如果某些东西在 .NET 4.5 中已过时,那么我看不出它在 .NET 4.0 中为什么以及如何不同,即使用任务并行库(没有async/await 参与和 .NET 4.5独家支持)

我很高兴在他的代码项目文章"Task Parallel Library and async-await Functionality - Patterns of Usage in Easy Samples" 中找到 Nick Polyak 的答案:

“.NET 4.5 的大部分功能已更改为任务,BackgroundWorker 现在几乎已过时,但您仍然可能会遇到一些需要使用 EAP 模式的情况。在这种情况下,最好从 EAP 功能中生成任务对象,这样您就可以安排它们并行运行或等待许多先前的任务完成等。在本节中,我们将展示如何实现它” “让任务功能使 BackgroundWorker 功能更大程度过时 - 您可以使用任务而不是 BackgroundWorker 来实现您需要的任何事情。您仍然可能有某些原因想要使用 BackgroundWorker 功能在团队中——无论是因为您的遗留代码使用它,还是因为您的大多数团队成员或您的老板都喜欢它并且比新的任务功能更好地理解它”

仍然愿意看到任何不同的争论观点

【讨论】:

if something is obsolete in .NET 4.5, then I do not see why and how it can be different in .NET 4.0 - 我将“过时”定义为“与替代品相比没有优势”。所以 BGW 在 .NET 4.0 中并没有过时,因为(在某些情况下)它比使用 TaskTaskScheduler 等更容易/更简单。所以它具有简单的优点(在某些情况下),因此不会过时。但它在 .NET 4.5 中已过时,因为新的 await 功能使任务变得更容易; BGW 没有超过await Task.Run 的优势,所以我认为它在 .NET 4.5 中已经过时了。 我应该说“没有优势”除了它可以在表单设计器中使用(它存在于工具箱中,可以拖放到表单/服务上,而你可以双击添加事件处理程序)。我从未使用过该功能,因此我倾向于忽略它。 :) @StephenCleary,BGW 组件仅存在于“Windows 窗体应用程序”的工具箱中,但不适用于“WPF 应用程序”项目。你可以猜到我在 .NET 4.0 中使用 Async CTP,最终我几乎所有的疑虑都归结为 What are the major risks vs. benefits of using VS2010 Async CTP?,而 .NET 4.5 中的差异如此之大,以至于我无法使用 Async CTP 在 .NET 4.0 中重现?

以上是关于是不是 .NET 4.0 TPL 让 APM、EAP 和 BackgroundWorker 异步模式过时了?的主要内容,如果未能解决你的问题,请参考以下文章

我已成功地将 CefSharp 嵌入到 .NET 4.0 应用程序中。是不是可以让 jQuery 调用在 DOM 上工作?

.NET中的异步

TPL-Dataflow 是不是适用于高并发应用程序?

如何使用 C# 4.0 编译器让 MonoDevelop 以 .NET 2.0 框架为目标?

不在 TPL Task 对象上调用 Dispose() 是不是可以接受?

在 TPL 中快速抛出未处理的异常