2011-09-06 83 views
34

我想更好地理解我在C#中使用的异步和并行选项。在下面的片段中,我列出了我遇到的最多的5种方法。但我不知道该如何选择 - 或者更好的是,在选择时要考虑什么样的标准:处理列表的C#异步选项

方法1:任务

(见http://msdn.microsoft.com/en-us/library/dd321439.aspx

调用StartNew在功能上等同使用它的一个构造函数创建一个Task,然后调用Start来安排它执行。但是,除非创建和调度必须分开,否则S​​tartNew是推荐的简单性和性能方法。

TaskFactory的StartNew方法应该是创建和计划计算任务的首选机制,但对于那些创建和计划必须分开的情况,可以使用构造函数,然后将任务的开始方法可用于调度稍后执行的任务。

// using System.Threading.Tasks.Task.Factory 
void Do_1() 
{ 
    var _List = GetList(); 
    _List.ForEach(i => Task.Factory.StartNew(_ => { DoSomething(i); })); 
} 

方法2:QueueUserWorkItem

(见http://msdn.microsoft.com/en-us/library/system.threading.threadpool.getmaxthreads.aspx

系统内存让您可以排队尽可能多的线程池的请求。如果有更多的请求比线程池线程多,则额外的请求会保持排队,直到线程池线程可用。

可以放置在其中定义该方法的类的实例字段由排队方法所需的数据,也可以使用接受包含必要数据的对象的QueueUserWorkItem(WaitCallback,对象)的过载。

// using System.Threading.ThreadPool 
void Do_2() 
{ 
    var _List = GetList(); 
    var _Action = new WaitCallback((o) => { DoSomething(o); }); 
    _List.ForEach(x => ThreadPool.QueueUserWorkItem(_Action)); 
} 

方法3:Parallel.Foreach

(参见:http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx

并行类提供基于库的数据以便共同操作,如并行替换循环,每个循环以及执行一组语句。

对源enumerable中的每个元素调用一次body委托。它以当前元素作为参数提供。

// using System.Threading.Tasks.Parallel 
void Do_3() 
{ 
    var _List = GetList(); 
    var _Action = new Action<object>((o) => { DoSomething(o); }); 
    Parallel.ForEach(_List, _Action); 
} 

方法4:IAsync。的BeginInvoke

(参见:http://msdn.microsoft.com/en-us/library/cc190824.aspx

BeginInvoke的是异步的;因此,控件在被调用后立即返回给调用对象。

// using IAsync.BeginInvoke() 
void Do_4() 
{ 
    var _List = GetList(); 
    var _Action = new Action<object>((o) => { DoSomething(o); }); 
    _List.ForEach(x => _Action.BeginInvoke(x, null, null)); 
} 

方法5:BackgroundWorker的

(参见:http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

要为一个后台操作,添加一个事件处理程序事件的DoWork。在此事件处理程序中调用您的耗时操作。要启动该操作,请调用RunWorkerAsync。要接收进度更新通知,请处理ProgressChanged事件。要在操作完成时接收通知,请处理RunWorkerCompleted事件。

// using System.ComponentModel.BackgroundWorker 
void Do_5() 
{ 
    var _List = GetList(); 
    using (BackgroundWorker _Worker = new BackgroundWorker()) 
    { 
     _Worker.DoWork += (s, arg) => 
     { 
      arg.Result = arg.Argument; 
      DoSomething(arg.Argument); 
     }; 
     _Worker.RunWorkerCompleted += (s, arg) => 
     { 
      _List.Remove(arg.Result); 
      if (_List.Any()) 
       _Worker.RunWorkerAsync(_List[0]); 
     }; 
     if (_List.Any()) 
      _Worker.RunWorkerAsync(_List[0]); 
    } 
} 

我想明显的critieria是:

  1. 是比任何其他获得更好的性能?
  2. 错误处理是否比其他更好?
  3. 对于监测/反馈,是否比另一个更好?

但是,如何选择? 提前感谢您的见解。

+1

你也可以看看System.Reactive(被动扩展,或rx.net)。 – lbergnehr

+0

而你甚至没有碰过异步CTP! :-) – xanatos

+0

的确如此,但我坚持使用C#4。好点,但。 –

回答

4

您的第一个,第三个和第四个示例隐式使用ThreadPool,因为默认情况下,任务安排在ThreadPool上,并且TPL扩展也使用ThreadPool,但API仅仅隐藏了一些复杂性,请参阅herehere。 BackgroundWorkers是ComponentModel名称空间的一部分,因为它们用于UI场景。

-2

最后一个是最好的2,3。它有内置的方法/属性。 其他变体几乎相同,只是不同的版本/简便的包装

+0

为了防御,我们使用BackgroundWorker在大部分代码中完成简单的异步操作。它为我们提供了许多版本的框架。但是,Do_5()中的方法绝不会以并行方式运行,只是异步。顺便提一下,这些其他方法具有非常相似的工具。我今天选择BackgroundWorker的原因是熟悉的。它不能帮助我的代码更好地执行,因为它的脚手架看起来很重。 –

2

反应性扩展是另一个即将出现的用于处理异步编程的库,特别是涉及异步事件和方法的组合时。

这不是本地的,但它是由实验室女士开发的。它可用于.NET 3.5和.NET 4.0,本质上是.NET 4.0引入的IObservable<T>接口上的一组扩展方法。

他们的主站点上有很多示例和教程,我强烈建议检查其中的一些。这个模式起初看起来有点奇怪(至少对于.NET程序员来说),但是非常值得,即使它只是在理解新概念。

被动扩展(Rx.NET)的真正优势在于您需要编写多个异步源和事件。所有的操作员都是在设计时考虑到这一点,并为您处理不同步的丑陋部分。

主要网站:http://msdn.microsoft.com/en-us/data/gg577609

初学者指南:http://msdn.microsoft.com/en-us/data/gg577611

例子:http://rxwiki.wikidot.com/101samples

这就是说,最好的异步模式可能取决于你在什么情况下有些是更好的(简单)为更简单的东西,有些更复杂和更容易处理,当涉及到更复杂的情况。我不能为你提到的所有人说话。

+4

这个答案非常需要一些上下文。直到第三段才提到你所说的“它”。 –

+0

固定。对不起,最初是作为对评论的回答,并且脱离了语境。 – lbergnehr

15

要采取这些以任意顺序:

BackgroundWorker (#5)
我喜欢用BackgroundWorker的时候我在做事情的UI。它具有的优点是在UI线程上触发进度和完成事件,这意味着当您尝试更改UI元素时不会遇到讨厌的异常。它还有一个很好的内置报告进度的方式。这种模式的一个缺点是,如果您在工作中阻止了呼叫(如网络请求),那么当工作正在进行时,您将有一个线程无所事事。这可能不是一个问题,如果你只认为你会有一小部分。

IAsyncResult/Begin/End (APM, #4)
这是使用广泛,功能强大但难以模型。错误处理很麻烦,因为您需要重新捕获End调用中的异常,而未捕获的异常不一定会将其返回到可处理它的任何相关代码片段。这具有在ASP.NET中永久挂起请求的危险,或者只是在其他应用程序中神秘地消失了错误。您还必须对CompletedSynchronously财产保持警惕。如果您没有正确跟踪和报告,程序可能会挂起并泄漏资源。另一方面,如果你在另一个APM的上下文中运行,你必须确保你调用的任何异步方法也报告这个值。这意味着要做另一个APM呼叫或使用Task并将其转换为IAsyncResult以获得其CompletedSynchronously属性。

签名中还有很多开销:如果要编写支持轮询和等待句柄的异步方法,您必须支持任意对象通过,创建自己的IAsyncResult实现(即使您只使用回调)。顺便说一下,你应该只在这里使用回调。当您使用等待句柄或轮询IsCompleted时,您在等待操作时浪费线程。

Event-based Asynchronous Pattern (EAP)
一个,这不是你的名单上,但我会提到为完整起见。它比APM有点友好。有事件而不是回调,挂在方法签名上的垃圾更少。错误处理更容易一些,因为它在回调中保存并可用,而不是重新抛出。 CompletedSynchronously也不是API的一部分。

Tasks (#1)
任务是另一种友好的异步API。错误处理很简单:在回调中总是检查异常,并且没有人关心CompletedSynchronously。您可以执行依赖关系,这是处理多个异步任务执行的好方法。你甚至可以在它们中包装APM或EAP(你错过的一种类型)异步方法。使用任务的另一个好处是你的代码不关心操作是如何实现的。它可能会阻塞某个线程或完全异步,但消费代码并不关心这一点。您还可以轻松地将APM和EAP操作与任务结合使用。

的Parallel.For方法(#3)
这些是任务的顶部附加助手。如果您的异步任务适合在循环中运行,他们可以为您创建任务并使代码更具可读性。

ThreadPool.QueueUserWorkItem(#2)
这是实际上是为所有请求使用ASP.NET一个低级别的实用程序。它没有像任务一样的内置错误处理,所以如果你想了解它,你必须抓住所有东西并将其传回给你的应用程序。它适用于CPU密集型工作,但不希望对其进行任何阻塞调用,例如同步Web请求。这是因为只要它运行,它就会使用一个线程。

async/await Keywords
新的.NET 4.5,这些关键字让你写代码的异步没有明确的回调。您可以等待Task,并且它下面的任何代码都将等待异步操作完成,而不消耗线程。

+0

我认为BackgroundWorker(#5)涵盖了EAP。但是,当然,感谢您对这个问题的见解。 –

+0

BackgroundWorker实现EAP,但不完全“覆盖”它。有了BackgroundWorker,你必须有一个同步的DoWork方法,但你可以在你处理事情的时候做一个EAP调用。它可以像web调用一样开始另一个异步操作,然后返回到另一个线程并触发事件。某些情况可能比BackgroundWorker更令人满意。 – RandomEngy