C# 从外部取消任务中的循环
在WinForm上,我需要从源(在我的例子中是USB设备)连续读取数据,并在标签中显示数据。读取应在命令(按钮单击)时开始,并在另一个按钮单击时停止,或以关闭方式停止。同时,我发现我需要使用C# 从外部取消任务中的循环,c#,multithreading,winforms,C#,Multithreading,Winforms,在WinForm上,我需要从源(在我的例子中是USB设备)连续读取数据,并在标签中显示数据。读取应在命令(按钮单击)时开始,并在另一个按钮单击时停止,或以关闭方式停止。同时,我发现我需要使用Task.Factory来实现这一点,因为我可以在那里创建CancellationToken。以下是我迄今为止的(示例)代码: public partial class Form1 : Form { CancellationTokenSource m_CancellationSource; T
Task.Factory
来实现这一点,因为我可以在那里创建CancellationToken
。以下是我迄今为止的(示例)代码:
public partial class Form1 : Form
{
CancellationTokenSource m_CancellationSource;
Task m_USBReaderTask;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
m_CancellationSource = new CancellationTokenSource();
m_USBReaderTask = Task.Factory.StartNew(() => doAsync(m_CancellationSource.Token), m_CancellationSource.Token);
}
private void doAsync(CancellationToken ct)
{
InitUSBReader();
while (!ct.IsCancellationRequested)
{
int[] data=ReadUSB();
this.Invoke((MethodInvoker)delegate
{
lbOut1.Text = data[0].ToString();
lbOut2.Text = data[1].ToString();
lbOut3.Text = data[2].ToString();
//... and so on...
});
}
CleanupUSBReader(); //this is never happening
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (null != m_CancellationSource)
{
m_CancellationSource.Cancel();
m_USBReaderTask.Wait(); // this always hangs.
}
}
}
显然,我面临两个问题:
CancellationToken
时,任务被中止,但我需要
清理一下,我只想结束while循环。(或者它崩溃并且没有错误消息?)FormClosing
事件中,我需要等待清理完成
已完成,但它会无休止地阻塞谢谢你的第一期。当令牌被取消时,While循环将结束,并且应该运行
CleanupUSBReader()
方法。这是假设ReadUSB定期返回,否则您将需要某种方法取消读取。如果仅在表单关闭时取消任务,则问题可能是死锁,请参见第二段。如果ReadUSB返回,您不会死锁,并且仍然没有到达清理方法,那么肯定会有其他问题,比如某个地方出现异常
关于你的第二个问题。问题是调用this.Invoke,这是同步的,即它将在主线程上运行代码,并等待它完成。因此,当窗体关闭时,主线程请求取消任务并等待它完成,但任务正在等待主线程更新UI。这将导致分类死锁。一种解决方案应该是使用this.BeginInvoke
,因为这要求主线程更新UI,但不等待结果。阅读更多
一般建议避免使用
task.Wait()
,因为这很容易导致这样的死锁。如果表单正在关闭,最好跳过等待任务。或者要取消关闭,请等待任务,并在等待后关闭表单。关于您的第一期。当令牌被取消时,While循环将结束,并且应该运行CleanupUSBReader()
方法。这是假设ReadUSB定期返回,否则您将需要某种方法取消读取。如果仅在表单关闭时取消任务,则问题可能是死锁,请参见第二段。如果ReadUSB返回,您不会死锁,并且仍然没有到达清理方法,那么肯定会有其他问题,比如某个地方出现异常
关于你的第二个问题。问题是调用this.Invoke,这是同步的,即它将在主线程上运行代码,并等待它完成。因此,当窗体关闭时,主线程请求取消任务并等待它完成,但任务正在等待主线程更新UI。这将导致分类死锁。一种解决方案应该是使用this.BeginInvoke
,因为这要求主线程更新UI,但不等待结果。阅读更多
一般建议避免使用
task.Wait()
,因为这很容易导致这样的死锁。如果表单正在关闭,最好跳过等待任务。或者要取消关闭,等待任务,并在等待后关闭表单。这里有两种方法可以重构代码,以避免使用丑陋的this.Invoke((MethodInvoker)委派
技术。如果USB读卡器是自由线程,则可以在不同的线程池
线程中调用其每个方法,如下所示:
async Task LoopAsync(CancellationToken ct)
{
await Task.Run(() => InitUSBReader(), ct);
while (!ct.IsCancellationRequested)
{
int[] data = await Task.Run(() => ReadUSB(), ct);
lbOut1.Text = data[0].ToString();
lbOut2.Text = data[1].ToString();
lbOut3.Text = data[2].ToString();
await Task.Delay(100);
}
await Task.Run(() => CleanupUSBReader(), ct);
}
m_USBReaderTask = LoopAsync(new Progress<int[]>(data =>
{
lbOut1.Text = data[0].ToString();
lbOut2.Text = data[1].ToString();
lbOut3.Text = data[2].ToString();
}), m_CancellationSource.Token);
private async void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (!m_USBReaderTask.IsCompleted)
{
e.Cancel = true;
this.Enabled = false;
m_CancellationSource.Cancel();
await m_USBReaderTask;
await Task.Yield(); // Ensure the asynchronous completion before Close
this.Close();
}
}
但是,如果USB读卡器需要线程关联,则上述方法将不起作用。要使其在单个线程中运行,您可以使用下面的技术,该技术还具有将UI代码与USB读卡代码分离的优点:
Task LoopAsync(IProgress<int[]> progress, CancellationToken ct)
{
return Task.Factory.StartNew(() =>
{
InitUSBReader();
while (!ct.IsCancellationRequested)
{
int[] data = ReadUSB();
progress.Report(data);
Thread.Sleep(100);
}
CleanupUSBReader();
}, TaskCreationOptions.LongRunning);
}
这里有两种方法可以重构代码,以避免使用丑陋的this.Invoke((MethodInvoker)delegate
技术。如果USB读卡器是自由线程,则可以在不同的ThreadPool
线程中调用其每个方法,如下所示:
async Task LoopAsync(CancellationToken ct)
{
await Task.Run(() => InitUSBReader(), ct);
while (!ct.IsCancellationRequested)
{
int[] data = await Task.Run(() => ReadUSB(), ct);
lbOut1.Text = data[0].ToString();
lbOut2.Text = data[1].ToString();
lbOut3.Text = data[2].ToString();
await Task.Delay(100);
}
await Task.Run(() => CleanupUSBReader(), ct);
}
m_USBReaderTask = LoopAsync(new Progress<int[]>(data =>
{
lbOut1.Text = data[0].ToString();
lbOut2.Text = data[1].ToString();
lbOut3.Text = data[2].ToString();
}), m_CancellationSource.Token);
private async void Form_FormClosing(object sender, FormClosingEventArgs e)
{
if (!m_USBReaderTask.IsCompleted)
{
e.Cancel = true;
this.Enabled = false;
m_CancellationSource.Cancel();
await m_USBReaderTask;
await Task.Yield(); // Ensure the asynchronous completion before Close
this.Close();
}
}
但是,如果USB读卡器需要线程关联,则上述方法将不起作用。要使其在单个线程中运行,您可以使用下面的技术,该技术还具有将UI代码与USB读卡代码分离的优点:
Task LoopAsync(IProgress<int[]> progress, CancellationToken ct)
{
return Task.Factory.StartNew(() =>
{
InitUSBReader();
while (!ct.IsCancellationRequested)
{
int[] data = ReadUSB();
progress.Report(data);
Thread.Sleep(100);
}
CleanupUSBReader();
}, TaskCreationOptions.LongRunning);
}
任务未中止。您必须编码任务将如何处理来自令牌的取消请求如果您发布可编译源,我将帮助您“设置cancellatontoken时,任务将中止”-非常确定它只是被标记为您希望它结束。不要将其视为线程。中止
。实际代码必须定期检查IsCancellationRequested
,以获得优雅的退出或。throwifcCancellationRequested()
半优雅。你真的需要展示一下等等所做的事情,因为据我们所知,你正在计算10000个位置的PI。我猜ReadUSB()
是一种阻塞方法,如果它不返回从未完成的任务。您必须使其可取消。另一个问题是竞态条件如果您在运行FormClosing
时尝试调用,则在事件处理程序等待invoke=deadlock时,直到事件处理程序结束调用才会发生。使用异步调用可能会有所帮助。为什么要这样做您将该方法命名为doAsync
?它不是一个异步方法(它不会返回任务
)。任务不会中止。您必须对任务如何处理来自令牌的取消请求进行编码如果您发布可编译源代码,我将帮助您“当设置cancellatontoken时,任务将中止”-非常好