任务:几千条(大量)数据往服务器数据库填写。要求单开线程执行,分割成小数据包,多线程运行。

实现方法:Parallel与TaskFactory都可以。

主要代码:

Parallel:

Barrier _bar;
int _maxLength = 20, _maxChannel = 2;//同时最多2条通道,每条通道最多20个数据
bool _isCancel = false;
private void btnWrite_Click(object sender, EventArgs e)
{
var tmpEmails = _emails.Where(x => !x.Value).Select(x => x.Key).ToList();
var state = 0; _isCancel = false;
SetControlEnable(false);
lblProgress.Text = "* 已完成 0%";
var channels = (tmpEmails.Count / _maxLength) + ((tmpEmails.Count % _maxLength > 0) ? 1 : 0);//总共多少条通道 var times = (channels / _maxChannel) + ((channels % _maxChannel > 0) ? 1 : 0);//单服务器分多次
new Action(() =>
{
for (int j = 0; j < times; j++)
{
if (_isCancel)
{
MessageBox.Show("任务取消!");
break;
}
var currChannel = Math.Min(_maxChannel, (channels - j * _maxChannel));//两者取其小的
_bar = new Barrier(currChannel);//根据次数设置栅栏
var tasks = new Action[currChannel];
for (int i = 0; i < currChannel; i++)
{
var subData = tmpEmails.Skip((i + j * _maxChannel) * _maxLength).Take(_maxLength).ToList();
tasks[i] = () =>
{
if (_isCancel) return;
var resMsg = 0;
Connect2WCF.RunSync(sc => resMsg = sc.UpdateMailState(subData, state));
if (resMsg == -1)
MessageBox.Show("保存失败了?详情可以查数据库日志表");
else if (resMsg == 0)
subData.ForEach(one => _emails[one] = true);//标记已经完成的。
new Action(() => txtEmails.Text = string.Join("\r\n", _emails.Where(x => !x.Value).Select(x => x.Key))).InvokeRun(this);
_bar.SignalAndWait();
};
}
Parallel.Invoke(tasks);
new Action(() => lblProgress.Text = "* 已完成 " + ((100 * (j + 1) / times)) + "%").InvokeRun(this);
}
new Action(() => SetControlEnable(true)).InvokeRun(this);
}).RunThread();
}

用Barrier和Parallel.Invoke结合来实现分割小数据包,每次用两个线程,每个线程传递20条数据,两个线程的数据都完成后,刷新完成的进度。isCancel作为取消操作的开关。实现的效果较下面的TaskFactory好。

TaskFactory:

CancellationTokenSource cts = new CancellationTokenSource();
int maxLength = 20, maxChannel = 2;//同时最多2条通道,每条通道最多20个数据
private void btnWrite_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
var tmpEmails = _emails.Where(x => !x.Value).Select(x => x.Key).ToList();
var state = 0; SetControlEnable(false);
lblProgress.Text = "* 已完成 0%";
var channels = (tmpEmails.Count / maxLength) + ((tmpEmails.Count % maxLength > 0) ? 1 : 0);//总共多少条通道 var times = (channels / maxChannel) + ((channels % maxChannel > 0) ? 1 : 0);//单服务器分多次
Action<List<string>, CancellationToken> doSave = (data, ct) =>
{
if (ct.IsCancellationRequested) return;
var msg = 0;
Connect2WCF.RunSync(sc => msg = sc.UpdateMailState(data, state));
if (msg == -1)
MessageBox.Show("保存失败了?详情可以查数据库日志表");
else if (msg == 0)
data.ForEach(one => _emails[one] = true);//标记已经完成的。
new Action(() => txtEmails.Text = string.Join("\r\n", _emails.Where(x => !x.Value).Select(x => x.Key))).InvokeRun(this);
}; for (int j = 0; j < times; j++)
{
int k = j;
if (cts.Token.IsCancellationRequested)
{
MessageBox.Show("任务取消!");
break;
}
var currChannel = Math.Min(maxChannel, (channels - j * maxChannel));//两者取其小的 TaskFactory taskFactory = new TaskFactory();
Task[] tasks = new Task[currChannel];
for (int i = 0; i < currChannel; i++)
{
var subData = tmpEmails.Skip((i + j * maxChannel) * maxLength).Take(maxLength).ToList();
tasks[i] = new Task(() => doSave(subData, cts.Token), cts.Token);
}
taskFactory.ContinueWhenAll(tasks,
x => new Action(() => lblProgress.Text = "* 已完成 " + ((100 * (k + 1) / times)) + "%").InvokeRun(this), CancellationToken.None);
Array.ForEach(tasks, x => x.Start());
}
SetControlEnable(true);
}

用TaskFactory和CancellationTokenSource结合来实现,在保存修改数据上,实现的效果和上面的方法差不多,但是在中间取消的效果上差很多,取消后,不会有“任务取消”的弹框。后台的执行逻辑猜测是这样:由于Task是单开线程跑,所以在btn的事件中, 所有Tasks和TaskFactory的声明基本上是很快就执行完成了的(电脑执行速度来看可能是一瞬间)。至于保存数据的代码,则在每个Task的后台线程中各自执行,此时操作的时间早已经跳出了btn的事件函数,于是,点击取消之后,由于btn的事件函数早已执行完,因此不会出现"任务取消"的弹框。而每个Task的执行受到线程个数的限制以及每个TaskFactory的ContinueWhenAll函数的监视,它们是有先后顺序但是却又无序地执行。点击取消后,可能有几个线程正在执行保存数据的任务,已经跳过了cancel的判断,所以取消的命令不会立刻反应到后台执行中,会有一部分任务在取消后,仍然在运行。而剩下的其他任务会判断cancel之后取消。由于线程的执行速度不是固定的,因此,小数据包保存执行的顺序虽然大概按照增序执行,但是细节的排序可能有些插队。

所以,总体而言TaskFactory的执行顺序不可控。断点不可控。而parallel.Invoke函数只有在传入的Action[]全部执行完之后,才会返回,所以有效的保证了大层面的执行顺序。至于Action[]这个队列执行的顺序,在Parallel里面也是不可控的。

补充:4092条数据,开启一个通道时,TaskFactory:Parallel = 19:25;

开启5个通道时,多次测试的结果为TaskFactory:Parallel = {18,16,15}:{19,16,15},速度差不多。

一个明显的现象:在数据很多的时候,可以清晰的看到TaskFactory中已完成的百分数出现忽大忽小的情况。例如:1,4,7,12,17,6,12,19,23...

另外,Parallel刚开始执行时,有明显的停顿感,猜测可能是启动并行时产生的效率损耗。

如果希望能够操作过程中能暂停处理,可以使用Parallel,它有一个执行主线程,方便随时停止。如果没有暂停需要,而且电脑的核心数不多(只有一个)时,可以考虑用TaskFactory,效率要明显高于Parallel。

c#执行并行任务之Parallel与TaskFactory的更多相关文章

  1. 并行模式库PPL应用实战(一):使用task类创建并行任务

    自 VS2010 起,微软就在 CRT 中集成了并发运行时(Concurrency Runtime),并行模式库(PPL,Parallel Patterns Library)是其中的一个重要组成部分. ...

  2. 第九节:深究并行编程Parallel类中的三大方法 (For、ForEach、Invoke)和几大编程模型(SPM、APM、EAP、TAP)

    一. 并行编程 1. 区分串行编程和串行编程 ①. 串行编程:所谓的串行编程就是单线程的作用下,按顺序执行.(典型代表for循环 下面例子从1-100按顺序执行) ②. 并行编程:充分利用多核cpu的 ...

  3. 异步和多线程,委托异步调用,Thread,ThreadPool,Task,Parallel,CancellationTokenSource

    1 进程-线程-多线程,同步和异步2 异步使用和回调3 异步参数4 异步等待5 异步返回值 5 多线程的特点:不卡主线程.速度快.无序性7 thread:线程等待,回调,前台线程/后台线程, 8 th ...

  4. 【读书笔记】.Net并行编程高级教程--Parallel

    一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...

  5. Parallel.Invoke并行你的代码

    Parallel.Invoke并行你的代码 使用Parallel.Invoke并行你的代码 优势和劣势 使用Parallel.Invoke的优势就是使用它执行很多的方法很简单,而不用担心任务或者线程的 ...

  6. gulp顺序执行任务

    gulp的任务的执行是异步的. 所以,当我写完一系列的任务,准备一股脑地执行. # gulp.task('prod', ['clean', 'compass', 'image', 'style', ' ...

  7. Omnithreadlibary学习(3)-IOmniTask异步执行SendMessage

    在任务中发送消息, 可以是函数或者对象方法 TOmniTaskMessageEvent = procedure(const task: IOmniTaskControl; const msg: TOm ...

  8. Net并行编程高级教程--Parallel

    Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控 ...

  9. ORA-12805: parallel query server died unexpectedly ORA-04030 (sort subheap,sort key) 原因排查与解决方法

    今日,某服务器pga调整为30G,_pga_max_size调整为8G之后(原来是2G,但是one passes语句较多,性能太低),执行出现ORA-12805: parallel query ser ...

随机推荐

  1. ajax.abort 终止AJAX请求

                               $(document).ready(function () { var ajax; $('#choice').change(function() ...

  2. asp.net MVC URL路由入门指南

    asp.net MVC 的URL路由是一个非常强大的功能,而且有个优点:强大单不复杂.然而,目前我在网上看到的相关资料,却都仅仅提供一些示例,仅通过这些示例,初学者基本上不可能明白为什么要这么配置,更 ...

  3. (转) VS2012程序打包部署详解

    程序编写测试完成后接下来我们要做的是打包部署程序,但VS2012让人心痛的是没有了打包工具.不知道出于什么原因微软没有将打包工具集成在开发环境中,但是我知道总会有解决办法的.     经过翻阅资料发现 ...

  4. NodeJs随心学习(一)之UEditor开源项目部署

    作为NodeJs的初学者+6年的码农,我觉得从基础看起太慢了,还是直接上Demo好玩一些. 嗯,直接开玩..今天在GitHub上看到了UEditor之NodeJs版的开源项目激起了我浓厚的兴趣,随后决 ...

  5. [转]JavaScript去重的6种方法

    Array.prototype.unique1 = function() { var n = []; for(var i = 0; i < this.length; i++) { if (n.i ...

  6. java多线程 join方法以及优先级方法

    /*join:当A线程执行到了B线程的.join()方法时,A就会等待.等B线程都执行完,A才会执行. join可以用来临时加入线程执行. 1.线程使用join方法,主线程就停下,等它执行完,那么如果 ...

  7. CentOS 安装jdk7

    1.下载jdk​​ http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html​ 选择jdk ...

  8. Swift 学习笔记1

    最近在看Swift,努力在看相关的文档以及书籍,因为Swift3.0的更新,以及它开源了,所以打算写一些关于Swift的相关文章.让Swift能够更好的被我理解

  9. Python3 多进程和多线程

    Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊.普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为 ...

  10. winfrom 多语言切换

    1.首先将窗体的“Localizable”属性置为“True”,然后将“Language”属性置为自己想要的语言,点击重新生成项目 例如:置为“中文”,以及“英文”.当每次置为不同的语言并重新生成项目 ...