C#处理列表的异步选项

Jer*_*xon 34 c# parallel-processing multithreading asynchronous

我想更好地理解我在C#中的Async和Parallel选项.在下面的片段中,我列出了我最常遇到的5种方法.但我不确定选择哪个 - 或者更好的是,在选择时要考虑的标准:

方法1:任务

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

调用StartNew在功能上等同于使用其构造函数之一创建Task,然后调用Start来安排执行.但是,除非必须分离创建和调度,否则StartNew是简化和性能的推荐方法.

TaskFactory的StartNew方法应该是创建和调度计算任务的首选机制,但是对于必须分离创建和调度的场景,可以使用构造函数,然后可以使用任务的Start方法来安排任务以便稍后执行时间.

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

方法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));
}
Run Code Online (Sandbox Code Playgroud)

方法3:Parallel.Foreach

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

Parallel类为常见操作提供基于库的数据并行替换,例如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);
}
Run Code Online (Sandbox Code Playgroud)

方法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));
}
Run Code Online (Sandbox Code Playgroud)

方法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]);
    }
}
Run Code Online (Sandbox Code Playgroud)

我认为明显的批评是:

  1. 性能是否优于其他?
  2. 错误处理是否优于其他?
  3. 监测/反馈是否优于其他?

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

Ran*_*ngy 15

要以任意顺序采取这些:

BackgroundWorker(#5)
我喜欢在使用UI时使用BackgroundWorker.它的优点是在UI线程上触发进度和完成事件,这意味着当您尝试更改UI元素时,不会出现令人讨厌的异常.它还有一个很好的内置报告进度的方式.这种模式的一个缺点是,如果你的工作中有阻塞调用(如Web请求),那么在工作发生时你就会有一个线程无所事事.如果您认为自己只有少数几个,那么这可能不是问题.

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

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

基于事件的异步模式(EAP)
一个不在你的列表中的但我会提到为了完整性.它比APM更友好一些.有事件而不是回调,并且方法签名上挂起的垃圾更少.错误处理稍微容易一些,因为它在回调中保存并可用,而不是重新抛出.CompletedSynchronously也不是API的一部分.

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

Parallel.For方法(#3)
这些是在Tasks之上的额外帮助程序.如果您的异步任务适合在循环中运行,他们可以执行一些工作来为您创建任务并使您的代码更具可读性.

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

async/ awaitKeywords
.NET 4.5中的新增功能,这些关键字允许您在没有显式回调的情况下编写异步代码.您可以等待aTask,它下面的任何代码都会等待异步操作完成,而不会消耗线程.


Has*_*niH 4

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