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

20

我有两种基于C# WPF应用程序的项目:

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

它们都应该生成2-10个长时间运行(数天)的进程,用户可以取消和重新启动这些进程。

我希望遵循最佳设计实践。首先,我现在对BackgroundWorker的使用感到困惑,但是我的问题应该适用于其他异步模式。

我看到(矛盾的)关于以下几点的不同观点:

异步模式: “基于async的异步编程方法在几乎所有情况下都优于现有的方法。特别是对于I/O绑定操作,这种方法比BackgroundWorker更好,因为代码更简单,而且您不必防范竞争条件。与Task.Run结合使用,相比CPU绑定操作,异步编程要比BackgroundWorker更好,因为异步编程将运行代码的协调细节与Task.Run传输到线程池的工作分离开来。”

我仍然有疑虑:

  1. 这些模式(首先是BGW)在.NET 4.5中是否已过时?
  2. 如果它们在.NET 4.5中已过时,为什么它们在.NET 4.0中还没有过时?

    2A)我是否错误地理解了.NET 4.5的新功能仍然可以在.NET 4.0中“轻松”实现/再现?

3个回答

17
我通常推荐在使用.NET 4.5时使用Task和/或await。但是Task和BGW有两个截然不同的场景。Task适用于一般的短异步任务,可以链接到一个连续体,而await适用于隐式地将任务编组回UI线程。BGW适用于单个长时间操作,不应影响您的UI的响应能力。您可以将BGW拖放到设计表面并双击以创建事件处理程序。如果您不想编组到另一个线程,则无需处理LongRunningConfigureAwait。许多人发现BGW进度比更容易。
以下是在“漫长操作”场景中同时使用两者的一些示例:
由于问题特别提到了.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<T> 会使代码与特定的UI框架(即对 BeginInvoke 的调用)解耦,就像BackgroundWorker 有助于从特定的UI框架中解耦一样。如果您不关心这个问题,那么您就不需要引入使用 Progress<T> 的复杂性。
至于 LongRunning,正如Stephen Toub所说:“您通常只会在通过性能测试发现不使用它会导致其他工作处理时间过长时才使用LongRunning”,因此,如果您发现需要使用它,则使用它-没有额外的分析或只是“复杂性”始终添加 LongRunning 参数。不使用 LongRunning 意味着用于长时间运行操作的线程池线程将无法用于其他更短暂的任务,并且可能会强制线程池在启动另一个线程(至少第二个)时延迟启动其中一个短暂任务。
框架中没有特别指出 BGW(或EAP、APM)已经过时的特性。因此,您需要决定何时将这些内容视为“过时”。BGW具有非常特定的使用场景,这仍然适用于它。在 .NET 4.0和4.5 中有相当不错的替代方法,但我并不认为 BGW 是“过时”的。
我不是说一定要使用BackgroundWorker,我只是在说,在废弃BackgroundWorker之前,请先考虑。在某些情况下,它可能是更好的选择。

1
不知道为什么所有的答案都转向.NET 4.5,尽管问题明确针对.NET 4.0,并且有很明显的原因和目的——简化和快捷地分析、重构和设计决策。相关的重要问题,比如“我在C# 5中可以用.NET 4.5做什么,而在C# 4中不能用.NET 4做什么?”(https://dev59.com/wGgu5IYBdhLWcg3wJDyb)正在被恶意攻击和关闭。然而,这些问题并不是那么明显,因为.NET 4.5无法安装/运行在Windows XP上。 - Gennady Vanin Геннадий Ванин
@Геннадий-Ванин 可能是因为 OP 暗示某些东西在 4.5 中被认为已经过时了(但从技术上讲并不是)。 - Peter Ritchie
稍微扩展一下,如果你想要做的工作涉及到通知变化的事件,你可以在Progress<>变量上进行闭包操作:processor.ItemProcessed += (sender, e1) => ((IProgress<int>) p).Report(e1.NextItem);,然后它就可以正常工作了 ™。 - Chris Marisic
你如何将后台工作器拖放到设计表面上?我已经查过了,它应该在工具箱的组件选项卡中,但是那个选项卡没有显示出来。 - Kyle Delaney
1
@KyleDelaney 在 WinForms 应用程序中?我认为 BackgroundWorker 仅适用于WinForm表单。 - Peter Ritchie
哦,我试图在WPF中完成它。 - Kyle Delaney

16
我认为这些模式(特别是APM,EAP和BGW)在.NET 4.5中已经过时。使用asyncTask.Run相结合的方式在所有方面都优于BGW。实际上,我刚刚开始了我的博客系列,在其中我将比较BGW和Task.Run,并展示它在每种情况下都更加麻烦;有些情况下它只是稍微更麻烦一些,但还有其他情况下它则更加麻烦。
现在,它们是否在.NET 4.0中已经过时是另一个完全不同的问题。从您的其他帖子中可以看出,您正在谈论使用VS2010开发.NET 4.0,因此回溯Microsoft.Bcl.Async不是一个选项。在这种情况下,我认为APM和EAP都不能被视为过时。在这个平台上,您可以考虑使用Task.Factory.StartNew作为BGW的替代品,但是在进度报告和自动线程调度其进度和完成事件方面,BGW确实具有一些优势。 更新:我最近更新了一篇旧博客文章,在其中讨论了各种后台操作的实现。在那篇文章中,当我谈到“任务(异步方法)”时,我指的是使用带有.NET 4.5 async支持、Task.Run等的Task。而“任务(任务并行库)”部分则评估了.NET 4.0中存在的Task

4
如果使用.NET 4.5,我通常会建议使用Task和/或await。但是Task和BGW有两种不同的情景。Task适用于一般的短异步任务,可以链接到后续操作,而await适用于隐式地将任务调度回UI线程的任务。BGW适用于单个长时间操作,不应影响UI的响应性。你可以将BGW拖放到设计表面并双击创建事件处理程序。如果你不想跨线程进行调度,你不必处理LongRunning或ConfigureAwait。许多人发现BGW的进度比IProgress<T>更容易掌握。 - Peter Ritchie
@Peter-Ritchie,请将您的评论作为答案发布。我对长时间运行的操作场景很感兴趣,但我无法清楚地理解“过时”宣言的解释和原理,以及何时、为什么和在哪里使用。特别是在新概念的背景下,这非常令人困惑。 - Gennady Vanin Геннадий Ванин
1
@ГеннадийВанинНовосибирск:Task.Run 在长时间运行的任务中非常有效,特别是在桌面应用程序中。在这种情况下,LongRunningConfigureAwait 都是不必要的。 - Stephen Cleary
但是后台工作程序可以从工具箱拖到设计器中,对吧?asyncTask.Run能够复制这个功能吗? - Kyle Delaney
@KyleDelaney:不是的。asyncTask.Run期望你编写代码。 - Stephen Cleary

1
我的问题是:
难道不是.NET 4.0 TPL使得APM、EAP和BackgroundWorker异步模式过时了吗?
也就是说,我怀疑并询问确认或否定,如果在.NET 4.5中某些内容已经过时,那么我不明白为什么和如何在没有async/await参与和.NET 4.5支持的情况下,它可以在.NET 4.0中有所不同,即使用任务并行库。
我很高兴在Nick Polyak的codeproject文章"Task Parallel Library and async-await Functionality - Patterns of Usage in Easy Samples"中找到了他的答案。
  • "大部分.NET 4.5的功能已经改为使用Tasks和BackgroundWorker现在几乎已经过时了,但是您仍然可能遇到某些情况需要使用EAP模式。在这种情况下,将EAP功能转换为Task对象非常有用,这样您就可以将它们安排在并行运行或等待一些先前的任务完成等。在本节中,我们将展示如何实现"
  • "拥有Task功能使得BackgroundWorker功能基本过时 - 您可以使用Task来实现所需的任何功能,而不是使用BackgroundWorker。尽管如此,您仍然可能有一些原因要在团队中使用BackgroundWorker功能 - 可能是因为您的旧代码使用它,或者因为您的大多数团队成员或老板更喜欢并且更好地理解它,而不是新的Task功能"

仍然开放以查看任何不同的观点


2
如果在.NET 4.5中某些东西已经过时,那么我不明白为什么和如何它在.NET 4.0中会有所不同。我定义“过时”为“与替代方案相比没有优势”。因此,在.NET 4.0中,BGW并不过时,因为(在某些情况下)它比使用“Task”、“TaskScheduler”等更容易/简单。因此,在某些情况下具有简单性的优势,因此不会过时。但是,在.NET 4.5中,由于新的“await”功能使所有任务变得更加容易;BGW没有比“await Task.Run”更好的优势,因此我认为它在.NET 4.5中已经过时。 - Stephen Cleary
我应该说“没有优势”,除非它可以用于表单设计器(它存在于工具箱中,可以拖放到表单/服务上,并且您可以双击添加事件处理程序)。我从未使用过这个功能,所以我倾向于忽略它。 :) - Stephen Cleary
1
@StephenCleary,BGW组件仅存在于“Windows窗体应用程序”工具箱中,而不在“WPF应用程序”项目中。正如您所猜测的那样,我正在使用.NET 4.0中的Async CTP,并且几乎所有我的疑问都归结为使用VS2010 Async CTP的主要风险与收益是什么?,在.NET 4.5中有什么巨大的不同,以至于我不能在.NET 4.0中使用Async CTP复制它? - Gennady Vanin Геннадий Ванин

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