Wasn't it .NET 4.0 TPL that made APM, EAP and

2019-01-31 13:12发布

I have 2 kinds of C# WPF app projects:

  • based on .NET 4.0 that I cannot migrate to .NET 4.5
  • based on .NET 4.0 that I can migrate to .NET 4.5

All of them should spawn 2-10 long-running (days) processes which can be cancelled and re-launched by users.

I am interested to follow the best design practices. First of all, now, I am interested to disambiguate about BackgroundWorker usage though, I hope, my question should be valid about other asynchronous patterns.

I see (contradicting) concurrent points of view about

asynchronous patterns:

"The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than BackgroundWorker for IO-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with Task.Run, async programming is better than BackgroundWorker for CPU-bound operations because async programming separates the coordination details of running your code from the work that Task.Run transfers to the threadpool"

I am still in doubt:

  1. Are those patterns (first of all, BGW) obsolete in .NET 4.5 ?
  2. If they are obsolete in .NET 4.5 why aren't they obsolete in .NET 4.0?

    2A) Do I understand incorrectly that .NET 4.5 new features are still "easy" implementable/reproducible in .NET 4.0?

3条回答
Melony?
2楼-- · 2019-01-31 14:00

My question was:

Wasn't it .NET 4.0 TPL that made APM, EAP and BackgroundWorker asynchronous patterns obsolete?

i.e. a doubt, asking for confirmation or negation that if something is obsolete in .NET 4.5, then I do not see why and how it can be different in .NET 4.0, i.e. with Task Parallel Library (without async/await engagement and .NET 4.5 support exclusively)

And I am quite happy to find Nick Polyak's answers in his codeproject article "Task Parallel Library and async-await Functionality - Patterns of Usage in Easy Samples" stated:

  • "Much of .NET 4.5 functionality has been changed to Tasks and BackgroundWorker is all but obsolete now, but you still might come across some instances when it is necessary to use the EAP pattern. In such cases, it will be nice to produce Task objects out of the EAP functionality so that you'll be able to schedule them to run in parallel or wait for a number of previous Tasks to finish etc. In this section we show how to achieve it"
  • "Having Task functionality made BackgroundWorker functionality largerly obsolete - you can achieve whatever you need using a Task instead of a BackgroundWorker. Still there might be some reason you want to use BackgroundWorker functionality on the team - whether because your legacy code uses it or because most of your team members or your boss love it and understand it better than the newer Task functionality"

still being open to see any different argumented points of view

查看更多
甜甜的少女心
3楼-- · 2019-01-31 14:06

I generally recommend Task and/or await if using .NET 4.5. But Task & BGW have 2 distinctly different scenarios. Task is good for general short asynchronous tasks that could be chained to a continuation and await is good at tasks implicitly marshalling back to the UI thread. BGW is good for a single long operation that shouldn't affect the responsiveness of your UI. You can drag-drop a BGW onto design surface and double-click to create event handlers. You don't have to deal with LongRunning or ConfigureAwait if you don't want to marshal to another thread. Many find BGW progress easier than IProgress<T>.

Here's some examples of using both in a "lengthy operation" scenario:

Since the question specifically mentions .NET 4.0, the following is simple code that uses a Task to do a lengthy operation while providing progress to a 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;
                    });

Similar code with BackgroundWorker might be:

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

Now, if you were using .NET 4.5 you could use Progress<T> instead of the BeginInvoke call with Task. And since in 4.5, using await would likely be more readable:

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;

Using Progress<T> means the code is not coupled to a specific UI framework (i.e. the call to BeginInvoke) in much the same way that BackgroundWorker facilitates decoupling from a specific UI framework. If you don't care, then you don't need to introduce the added complexity of using Progress<T>

As to LongRunning, as Stephen Toub says: "You'd typically only use LongRunning if you found through performance testing that not using it was causing long delays in the processing of other work" so, if you find you need to use it, then you use it--there's the added analysis or just the "complexity" of always adding the LongRunning parameter. Not using LongRunning means the thread pool thread used for the long running operation won't be usable for other, more transient, tasks and could force the thread pool to delay starting one of these transient tasks while it starts up another thread (at least a second).

There's no attributes in the framework that specifically say that BGW (or EAP, or APM) are deprecated. So, it's up to you to decide where and when any of these things are "obsolete". BGW in particular always had a very specific usage scenario that still applies to it. You have fairly decent alternatives in .NET 4.0 and 4.5; but I don't really think BGW is "obsolete".

I'm not saying always use BackgroundWorker, I'm just saying think before you automatically deprecate BackgroundWorker, in some cases it might be a better choice.

查看更多
爷、活的狠高调
4楼-- · 2019-01-31 14:17

I consider these patterns (APM, EAP, and BGW in particular) obsolete in .NET 4.5. The combination of async with Task.Run is superior to BGW in every way. In fact, I just started a series on my blog where I will compare BGW to Task.Run and show how it's more cumbersome in every situation; there are some situations where it's just slightly more cumbersome, but there are other situations where it's much more cumbersome.

Now, whether they're obsolete in .NET 4.0 is another question entirely. From your other posts, you're talking about developing for .NET 4.0 with VS2010, so the backport Microsoft.Bcl.Async isn't an option. In that case, neither APM nor EAP can be considered obsolete IMO. On this platform, you can consider Task.Factory.StartNew as an alternative to BGW but BGW does have some advantages when it comes to progress reporting and automatic thread marshaling of its progress and completion events.

Update: I did recently update an old blog post of mine where I discuss various implementations of background operations. In that post, when I talk about "Tasks (Async Methods)", I mean using Task with all the .NET 4.5 async support, Task.Run, etc. The "Tasks (Task Parallel Library)" section is evaluating Task as it existed in .NET 4.0.

查看更多
登录 后发表回答