繁体   English   中英

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

[英]c# check if thread/background task has finished or needs aborting

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

  1. 从数据库获取要处理的项目列表
  2. 在后台启动一种方法(只需一个,就不需要更多)来处理一项
  3. 检查它是否已完成或需要终止(通过SQL查找)
  4. 完成/中止后重复2
  5. 如果没有了,睡一会儿,然后重复1

我对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;}

  }
}

//返回并重复

或在Task的情况下(类似,大致完成并从网络上捏住):

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()已被弃用,但文档中没有提及。

谢谢。

这通常称为生产者/消费者问题。 您有一个线程(您的主线程)将为后台线程(Consumer)产生许多项目以供继续使用。

.net的任务并行库中的BlockingCollection可能对此有用。 看看这篇文章: http : //blogs.msdn.com/b/csharpfaq/archive/2010/08/12/blocking-collection-and-the-producer-consumer-problem.aspx

另外,我建议您检查一下Darek在其帖子中建议的“并行扩展额外功能的管道”项目。

通常,与手动更新线程相比,我建议使用Tasks或ThreadPool(按该顺序)。 线程池旨在通过池化来减少构建多个线程的开销。 任务使用线程池。 您很少需要手动创建线程。

使用任务工厂。 您可以使用以下命令等待任务完成:

Task.WaitAll(task);

而不是示例代码中的while循环。

另外,CancellationToken用于向任务发出信号,通知它应该取消,因此您需要自己执行检查。

http://msdn.microsoft.com/EN-US/library/vstudio/dd997396(v=vs.100).aspx

但是,如果考虑使用Parallel Extensions Extras中的Pipeline也会更好,它也支持取消令牌。 这样,您可以从数据库检索记录,而不是将它们作为IEnumerable传递给管道,同时可以从单独的线程或内部线程中取消所有记录。 您可以从第一个记录开始处理,而其他记录则在后台检索。 管道将为其每个步骤创建一个后台任务,以处理每个元素。 每个步骤的默认并行度为1。 它非常快速和高效。

UPDATE

Dapper,并行扩展Extras和响应式扩展的小示例

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只是时间服务器。

但是,正如您所知道的,我并不是真正在使用线程或任务,所以我在这里回答作者的问题吗? 并不是的。 相反,我为他提供了另一种选择,我认为它更适合他的数据处理方案。

但是,如果必须选择,我仍然会坚持使用Task Factory,因为它比自己管理线程更精简,更方便。

希望这可以帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM