如何杀死或阻止后台工作人员?

时间:2018-08-03 14:01:20

标签: c# .net backgroundworker

为了快速加载几个大文件,我启动了backgroundworkers作为文件数。

每个后台工作人员都需要很长时间才能分别加载其文件。他们加载文件时,我想停止所有加载。我了解backgroundworker.CancelAsync()将取消消息发送到线程,但是线程没有时间接受消息。因为每个线程仅加载一个文件,所以没有循环操作来检查取消。在这种情况下,如何停止这些背景工作人员?

让我在这里显示我的代码。 //主线程调用50个子线程。

private List<BackgroundWorker> bgws = new List<BackgroundWorker>();
private bool ChildThreadCompleted;
 private void MainThread_DoWork(object sender, DoWorkEventArgs e)
{
// 50 sub threads will be started here
    for (int i=1; i<=50; i++)
    {
        if (mainThread.CancellationPending) return;
        BackgroundWorker childThread = new BackgroundWorker();
        childThread.WorkerSupportsCancellation = true;
        childThread.DoWork += ChildThread_DoWork;
        childThread.RunWorkerCompleted += ChildThread_RunWorkerCompleted;
        bgws.Add(childThread);
        childThread.RunWorkerAsync(i);
    }
    while (!ChildThreadCompleted)
    {
        if (mainThread.CancellationPending)
        {
            foreach (BackgroundWorker thread in bgws)
                if (thread.IsBusy) thread.CancelAsync();
        }
        Application.DoEvents();
    }
}

 private void ChildThread_DoWork(object sender, DoWorkEventArgs e)
{
    int arg = Convert.ToInt32(e.Argument);
    System.Threading.Thread.Sleep(1000);
    BackgroundWorker thread = (BackgroundWorker)sender;
    if (thread.CancellationPending) return;
    // In case of loading the image no longer makes sense, I'd like to stop.
    // At this point, i can't stop this process.
    //loading large file here. Just one image file for example. <= this takes one or more seconds

    e.Result = arg;
}

private void ChildThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    BackgroundWorker thread = sender as BackgroundWorker;
    bgws.Remove(thread);
    if (bgws.Count == 0) ChildThreadCompleted = true;
}

1 个答案:

答案 0 :(得分:1)

简短版本

BGW无法做到这一点,除非没有复杂的编码。 ActionBlock专门用于处理具有取消支持的输入流。

我使用数据流类每15分钟查找,下载和处理数千张机票记录。

长版

BackgroundWorker已过时,已完全由TPL类(例如任务,CancellationToken,IProgress等)取代。

这个问题最好由更高级别的课程ActionBlock解决。 使用ActionBlock和TPL Dataflow名称空间中的其他类,您可以创建类似于Powershell管道的块管道。

每个块运行自己的任务,接收输入并将输出传递到下一个块。您甚至可以指定一个块可以通过使用多个任务来处理多个输入。

ActionBlock不产生任何输出,它仅处理输入。像大多数块一样,它具有输入缓冲区并支持取消。

文件处理块可能看起来像这样。 :

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file));

var files = Directory.EnumerateFiles(someFolder,somePattern);

//Post all files
foreach(file in files)
{
    //Post doesn't block
    block.Post(file);
}    

使用完块后,我们应该告诉我们完成了:

block.Complete();

并异步等待,直到处理完所有剩余文件:

await block.Completion;

您可以通过指定并发任务的最大数量来指示块进入process multiple messages in parallel

var options = new ExecutionDataflowBlockOptions
     {
        MaxDegreeOfParallelism = 20
     };

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);

启动100个线程来处理100个文件可能会导致线程彼此阻塞。通过限制并发任务的数量,可以确保所有CPU都能执行有用的工作。

您也可以停止阻止。一种方法是通过调用Fault()方法来“ nuke”它:

block.Fault();
try 
{
    await block.Completion;
}
catch(Exception exc)
{
     ...
}

这将丢弃留在输入缓冲区中的所有内容,并将异常传播到管道中的下一个块。好像该块的方法引发了异常。在这种情况下,没有其他块,将await block.Completion;抛出。

另外,more cooperative way将使用CancellationTokenSource取消块 并向工作程序方法发出信号,指出应取消该块。

CancellationTokenSource是一个类,可用于向任何任务,线程或其他代码发出取消信号。通过提供一个CancellationToken,当有人在CTS上调用true或超时时间到期时,其IsCancellationRequested属性变为Cancel()的CancellationToken来实现。

这样,您可以通过创建具有超时期限的CTS为您的块提供超时功能:

var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
var options = new ExecutionDataflowBlockOptions
     {
        MaxDegreeOfParallelism = 20,
        CancellationToken=cts.Token
     };

var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file), options);

//.....

try
{
    await block.Completion;
}
catch (OperationCanceledException)
{
    Console.WriteLine("Timed out!");
}

如果CTS存储在字段中,则按钮事件可用于发出取消信号:     CancellationTokenSource _cts;     ActionBlock _block;

public void Start_Click(object sender, EventArgs args)
{
    //Make sure both the CTS and block are created before setting the fields  
    var cts=new CancellationTokenSource(60000); //Timeout in 1 minute
    var token=cts.Token;

    var options = new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = 20,
        CancellationToken=token
     };
   var block=new ActionBlock<string>(file => SomeSlowFileProcessing(file,token), 
                                     options);
   //Once preparation is over ...
   _cts=cts;
   _block=block;

   //Start posting files
  ...

}

public async void Cancel_Click(object sender, EventArgs args)
{
   lblStatus.Text = "Cancelling";
    _cts.Cancel();

   try
   {
       await _block.Completion;           
   }
   lblStatus.Text = "Cancelled!";
}

取消加载大文件

异步文件操作也接受取消令牌,例如FileStream.ReadAsync的重载接受CancellationToken

这意味着,如果将worker方法转换为异步方法,则可以将其取消,例如

async Task MySlowMethod(string fileName,CancellationToken token)
{
    try 
    {
        using (FileStream SourceStream = File.Open(filename, FileMode.Open))
        {
            var data = new byte[SourceStream.Length];
            await SourceStream.ReadAsync(data, 0, (int)SourceStream.Length,token);
            // Use the data
        }
}