c#检查线程/后台任务是否已完成或需要中止

时间:2014-10-27 20:41:15

标签: c# .net multithreading task

我对我的控制台/ Windows服务(我可以运行任一模式)应用程序有一个相当简单的要求:

  1. 从数据库中获取要处理的项目列表
  2. 在后台启动一个方法(只需一个,从不需要更多)来处理一个项目
  3. 检查它是否已完成或需要终止(通过SQL查找)
  4. 完成/中止后重复2次
  5. 不再时,请稍微睡一会,重复1次
  6. 我对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()已被弃用,但文档中没有提及。

    感谢。

2 个答案:

答案 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,因为它比自己管理线程更精简,更方便。

希望这有帮助。