我对我的控制台/ Windows服务(我可以运行任一模式)应用程序有一个相当简单的要求:
我对c#/ .net相当新,我可以看到各种各样的线程系统。对于这种情况,线程或任务哪个更好?
在线程的情况下,我假设它(每个粗略的代码)类似于每个要处理的项目:
Thread thread = new MyThread(new ThreadStart(this.SomeFunction));
thread.Start();
while(!finished) {
if (!thread.IsAlive())
finished=true;
else {
//check database for early termination of job
terminate=SomeChdck();
if(terminate) { thread.Abort(); finished=true;}
}
}
//返回并重复
或者在任务的情况下(再次,粗略地完成,并从网上捏):
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task task = Task.Factory.StartNew(this.SomeFunction, token);
while(!finished) {
if (task.IsCompleted)
finished=true;
else {
//check database for early termination of job
terminate=SoneChdck();
if(terminate) { tokenSource.Cancel(); finished=true;}
}
}
//返回并重复
这些方法是否存在差异(假设它们都可以正常工作),并且我在某处读过Thread.Abort()已被弃用,但文档中没有提及。
感谢。
答案 0 :(得分:2)
这通常被称为生产者/消费者问题。你有一个线程(你的主线程)将为你的后台线程(消费者)生成一些项目来接收和处理。
.net的任务并行库中的BlockingCollection对此非常有用。看看这篇文章:http://blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx
此外,我建议您查看Darek在其帖子中建议的“来自Parallel Extensions Extras的管道”项目。
一般情况下,我会推荐使用任务或ThreadPool(按此顺序)而不是手动新建一个线程。线程池旨在通过汇集它们来减少构造多个线程的开销。任务使用Threadpool。您需要手动创建线程非常罕见。
答案 1 :(得分:1)
使用任务工厂。您可以使用以下命令等待任务完成:
Task.WaitAll(task);
而不是示例代码中的while循环。
此外,CancelationToken用于向应该取消的任务发出信号,因此您需要自己执行检查。
http://msdn.microsoft.com/EN-US/library/vstudio/dd997396(v=vs.100).aspx
但是,如果考虑Pipeline from Parallel Extensions Extras,你也会感觉更好,它也支持取消令牌。这样,您可以从DB检索记录,而不是将它们作为IEnumerable传递给管道,而您可以从单独或内部线程中自由地取消它们。您可以使用第一条记录开始处理,而其余的则在后台检索。 Pipeline将为每个要处理的元素的每个步骤创建一个后台任务。每个步骤的默认并行度为1。它非常快速有效。
<强>更新强>
Dapper,Parallel Extensions Extras和Reactive Extensions的小例子
var pipeline = Pipeline.Create<SomeType, bool>(st =>
{
//Do something with st
return someBool; //some bool if you succeeded or not
});
var cts = new CancellationTokenSource();
//cancel after 10s (just for fun)
Observable.Timer(TimeSpan.FromSeconds(10)).Subscribe(s => cts.Cancel());
using (var conn = new SqlConnection("someConnectionString"))
{
conn.Open();
pipeline.Process(conn.Query<SomeType>("SOME SQL HERE", buffered:true),cts.Token).ToList();
}
这个选择的原因是为了证明使用Dapper是多么容易,Parallel Extensions Extras是多么强大和方便,但是对于你的样本,它是故意过度设计的... :)我希望你能原谅我。最后需要ToList(),否则不会对IEnumerable执行任何操作。或者你可以使用这种方法:
Console.WriteLine(
pipeline.Process(conn.Query<SomeType>("SOME SQL HERE", buffered: true), cts.Token).All(b => b)
? "All records processed successfully"
: "Some records failed");
如果要从数据处理步骤中取消,请先声明cts:
var cts = new CancellationTokenSource();
var pipeline = Pipeline.Create<SomeType,bool>(st =>
{
//Do something with st
//you could even cancel from here
if(someOtherBool)
cts.Cancel();
return someBool; //some bool if you succeeded or not for example
});
如果您不想声明特定类型:
var cts = new CancellationTokenSource();
var pipeline = Pipeline.Create<dynamic,bool>(d =>
{
//Do something with data
if(someOtherBool)
cts.Cancel();
return someBool; //some bool if you succeeded or not
});
using (var conn = new SqlConnection("someConnectionString"))
{
conn.Open();
foreach (var b in pipeline.Process(conn.Query("SOME SQL HERE", buffered: true), cts.Token))
{
Console.WriteLine(b?"Success":"Failure");
}
}
最后要提到的是cts.Cancel()通常会在内部线程上抛出异常,因此如果需要,请将管道包含在try / catch中。
更新2
在阅读作者的评论后,我仍然会选择Dapper,PEE和Rx(双关语)的组合。
var cts = new CancellationTokenSource();
var pipeline = Pipeline.Create<dynamic, dynamic>(d =>
{
//Do something with data in step 1
if (someConditionalCheck)
cts.Cancel();
return d;
}).Next<dynamic>(d =>
{
//do something with data is step 2
if(someConditionalCheck)
cts.Cancel();
return d;
});
subscription = Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(_ =>
{
try
{
using (var conn = new SqlConnection("someConnectionString"))
{
conn.Open();
foreach (var v in pipeline.Process(conn.Query("SOME SQL HERE", buffered: true), cts.Token))
{
//Do something with or ignore the result
}
}
}
catch (AggregateException e)
{
//Investigate what happened, could be error in processing
//or operation cancelled
}
catch (Exception e)
{
//All other exceptions
}
});
Rx让我创造一个整洁的观察者,每分钟都会开火。我也可以设计一个在上一次运行一段时间不活动后触发的情况,在这种情况下我更喜欢间隔。
PEE让我创建一个整洁的工作流程,在那里我可以指定对从数据库中检索的一个数据项执行的多个步骤。有权访问CancellationTokenSource,我可以在每个步骤完成后立即取消所有步骤,因此,如果一个记录在步骤1中而另一个记录在步骤N中,则一旦完成各自的代码块,两个记录都将被取消。
Dapper在与数据库交谈时只是一个时间服务器。
然而,正如你所知,我并不是真的使用Threads或Task,所以我在这里回答提交人的问题吗?并不是的。相反,我正在为他提供一种替代方案,我认为这更符合他的数据处理方案。
但如果我不得不选择,我仍然坚持使用Task Factory,因为它比自己管理线程更精简,更方便。
希望这有帮助。