为了快速加载几个大文件,我启动了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;
}
答案 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
}
}