19

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

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

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

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

我看到(矛盾的)关于

异步模式:

“在几乎所有情况下,基于异步的异步编程方法都比现有方法更可取。特别是,对于 IO 绑定操作,这种方法比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 中仍然“容易”实现/重现?

4

3 回答 3

15

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

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

由于问题特别提到了 .NET 4.0,以下是简单的代码,它使用 aTask执行冗长的操作,同时为 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,则可以使用 .NETProgress<T>代替BeginInvoke调用Task。从 4.5 开始, usingawait可能更具可读性:

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参数的“复杂性” 。不使用 LongRunning 意味着用于长时间运行操作的线程池线程将不能用于其他更临时的任务,并且可能会强制线程池在启动另一个线程时延迟启动其中一个临时任务(至少第二)。

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

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

于 2013-05-04T14:19:28.260 回答
15

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

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

更新:我最近确实更新了我的一篇旧博客文章,其中讨论了后台操作的各种实现。在那篇文章中,当我谈到“任务(异步方法)”时,我Task的意思是使用所有 .NET 4.5async支持Task.Run等。“任务(任务并行库)”部分正在评估Task,因为它存在于 .NET 4.0 中。

于 2013-05-03T17:09:15.437 回答
1

我的问题是:

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

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

我很高兴在他的 codeproject 文章“Task Parallel Library and async-await Functionality - Patterns of Usage in Easy Samples”中找到 Nick Polyak 的答案:

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

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

于 2013-05-05T11:49:24.893 回答