用于处理列表的C#asynchronous选项
我想更好地理解我在C#中使用的asynchronous和并行选项。 在下面的片段中,我列出了我遇到的最多的5种方法。 但是我不确定要select哪一个,或者更好,在select时要考虑什么标准:
方法1:任务
(请参阅http://msdn.microsoft.com/en-us/library/dd321439.aspx )
调用StartNew在function上等价于使用其中一个构造函数创build一个Task,然后调用Start来计划执行。 但是,除非创build和调度必须分开,否则StartNew是简单性和性能的推荐方法。
TaskFactory的StartNew方法应该是创build和调度计算任务的首选机制,但是对于必须将创build和调度分离的情况,可以使用构造函数,然后可以使用任务的Start方法将任务调度为稍后执行时间。
// 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,Object)重载。
// 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 )
Parallel类提供基于库的数据并行replace常见操作,例如for循环,每个循环和执行一组语句。
对于源枚举中的每个元素,都会调用一次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是asynchronous的; 因此,控件在被调用后立即返回给调用对象。
// 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]); } }
我想这个显而易见的批评是:
- 比其他performance更好吗?
- error handling是否比其他更好?
- 对于监测/反馈,有没有比其他更好的方法?
但是, 你如何select? 提前感谢您的见解。
您的第一个,第三个和第四个示例隐式使用ThreadPool,因为默认情况下,任务安排在ThreadPool上,而TPL扩展也使用ThreadPool,API只是简单地隐藏了一些复杂的内容。 BackgroundWorkers是ComponentModel名称空间的一部分,因为它们是用于UI场景的。
要采取任意的顺序:
BackgroundWorker(#5)
当我使用UI进行操作时,我喜欢使用BackgroundWorker。 它具有的优点是在UI线程上触发进度和完成事件,这意味着当您尝试更改UI元素时,不会遇到讨厌的exception。 它也有一个很好的内置报告进度的方式。 这种模式的一个缺点是,如果您在工作中阻止了呼叫(如networking请求),那么当工作正在进行时,您将有一个线程无所事事。 这可能不是一个问题,如果你只认为你会有一小撮。
IAsyncResult / Begin / End(APM,#4)
这是一个普遍而强大但难以使用的模式。 error handling很麻烦,因为您需要重新捕获End调用中的exception,未捕获的exception不一定会将其返回到可以处理它的任何相关代码片段。 这有在ASP.NET中永久挂起请求的危险,或者只是在其他应用程序中神秘地消失了错误。 您还必须对CompletedSynchronously
属性保持警惕。 如果您没有正确跟踪和报告,程序可能会挂起和泄漏资源。 另一方面是,如果你在另一个APM的上下文中运行,你必须确保你调用的任何asynchronous方法也报告这个值。 这意味着做另一个APM调用或使用一个Task
并将其转换为IAsyncResult
以获取其CompletedSynchronously
属性。
在签名中还有很多开销:如果要编写支持轮询和等待句柄的asynchronous方法(即使只使用callback函数),您必须支持任意对象才能通过,创build自己的IAsyncResult实现)。 顺便说一句,你只应该在这里使用callback。 当您使用等待句柄或轮询IsCompleted
,您正在浪费线程,而操作正在等待。
基于事件的asynchronous模式(EAP)
一个不在你的名单上,但我会提到的完整性。 这比APM有点友好。 有事件而不是callback,挂在方法签名上的垃圾更less。 error handling更容易一些,因为它在callback中保存并可用,而不是重新抛出。 CompletedSynchronously也不是API的一部分。
任务(#1)
任务是另一个友好的asynchronousAPI。 error handling非常简单:在callback中总是检查exception,没有人关心CompletedSynchronously。 您可以执行依赖关系,这是处理多个asynchronous任务执行的好方法。 你甚至可以在其中包装APM或EAP(你错过的一种types)asynchronous方法。 使用任务的另一个好处是您的代码不关心操作是如何实现的。 它可能阻塞在一个线程或完全asynchronous,但消费代码不关心这一点。 您还可以轻松地将APM和EAP操作与任务混合使用。
Parallel.For方法(#3)
这些是任务之外的辅助工具。 如果你的asynchronous任务适合循环运行,他们可以做一些工作来为你创build任务,并使你的代码更具可读性。
ThreadPool.QueueUserWorkItem(#2)
这是ASP.NET实际用于所有请求的低级实用程序。 它没有任何像任务一样的内置error handling,所以如果你想了解它,你必须抓住所有的东西,并把它传回给你的应用程序。 它适用于CPU密集型工作,但不希望对其进行任何阻塞调用,例如同步Web请求。 那是因为只要它运行,它就使用一个线程。
async
/ await
关键字
在.NET 4.5中新增了这些关键字,可以在不显式callback的情况下编写asynchronous代码。 您可以等待一个Task
,任何下面的代码将等待asynchronous操作完成,而不消耗一个线程。
反应式扩展是另一个即将出现的用于处理asynchronous编程的库,特别是涉及asynchronous事件和方法的组合时。
这不是本地的,但它是由实验室女士开发的。 它可用于.NET 3.5和.NET 4.0,实质上是.NET 4.0引入的IObservable<T>
接口上的一组扩展方法。
主站点上有很多示例和教程,我强烈build议检查其中的一些。 这个模式起初看起来有些古怪(至less对于.NET程序员来说),但是值得的是,即使它只是在抓住新的概念。
react native扩展(Rx.NET)的真正优势在于您需要编写多个asynchronous源和事件。 所有的操作员都是在devise时考虑到这一点,并为您处理不同步的丑陋部分。
主站点: http : //msdn.microsoft.com/en-us/data/gg577609
初学者指南: http : //msdn.microsoft.com/en-us/data/gg577611
示例: http : //rxwiki.wikidot.com/101samples
也就是说,最好的asynchronous模式可能取决于你处于什么样的状态。有些更简单,更简单,有些更复杂,更易于处理。 我不能为你提到的所有人说话。
最后一个是最好的2,3。 它有内置的方法/属性。 其他变体几乎是相同的,只是不同的版本/方便的包装