C#:取消 BeginInvoke()
Posted
技术标签:
【中文标题】C#:取消 BeginInvoke()【英文标题】:C#: Canceling BeginInvoke() 【发布时间】:2021-03-27 16:47:52 【问题描述】:我正在尝试让控件执行以下操作:
第一次点击开始显示文本:每 30 毫秒一个字符。 在第二次单击时取消该过程并立即显示全文。
这是我通过 BeginInvoke() 传递给控件的方法:
DBX_Dialogue.running = true;
for (int i = 0; i < line.text.Length; i++)
Text += line.text[i];
Thread.Sleep(30);
if (DBX_Dialogue.cancel)
Text = line.text;
DBX_Dialogue.running = false;
break;
DBX_Dialogue.running = false;
在主窗口的 OnClick() 方法中,我这样做:
if (DBX_Dialogue.running)
DBX_Dialogue.cancel = true;
return;
else
DelWrite delWrite = WriteLine;
DBX_Dialogue.BeginInvoke(delWrite);
但是,DBX_Dialogue.cancel 从未设置为 true,所以我想我的方法实际上并没有异步运行 - 我做错了什么?
【问题讨论】:
天哪,非常感谢!我处理这个问题的方式可以吗,还是有更好的方法?我将我的方法标记为异步,然后使用这一行:await Task.Delay(30); 如果您在循环中等待 30 秒(在后台线程上,使用Thread.Sleep
、await Task.Delay
或使用 WinForms 计时器,您仍然不会获得响应行为。看看您的代码。如果有人在您上次投票后立即设置DBX_Dialogue.cancel
,则在您看到它之前将是 30 秒。考虑使投票时间更快(例如 250 毫秒)并循环 4x30 次以标记您的 30 秒,或创建更多复杂的机制(可能使用WaitHandle.WaitAny
或Task.WaitAny
)来表示取消。
***.com/help/someone-answers
【参考方案1】:
简短的回答是,您正在调用的 Control.BeginInvoke()
方法调用 UI 线程上的委托。一旦调用了委托,UI 线程将无法执行其他代码,直到该委托的目标完成执行。
因此,如果您引用的 OnClick()
方法应该在该 UI 线程拥有的控件中处理单击事件,则该事件处理程序无法执行,因为 UI 线程在委托调用上卡住了。
还有一个为任何委托类型定义的BeginInvoke()
方法。那个将在线程池上排队委托调用。可能这就是你要打电话的那个?看起来像这样:
delWrite.BeginInvoke();
当然,当您这样做时,您会遇到从线程池线程访问 UI 对象的问题。然后你需要在那里使用Control.Invoke()
,只针对那些特定的语句。网站上有很多问题和答案都在讨论该技术。
您的问题缺乏很多细节,无法帮助我们其他人理解完整的场景并提供更好的建议。事实是,代码的基本结构看起来很可疑,几乎可以肯定有更好的方法不涉及调用任何BeginInvoke()
方法。但是如果没有更多细节,就不可能确切地知道那会是什么样子。您可以查看 Task Parallel Library 和 async
/await
之类的东西,了解与 UI 线程协调异步工作的现代方法。
以下是关于您尝试做的另一种现代实施方式的疯狂猜测:
private CancellationTokenSource tcs;
async Task Run(CancellationToken token)
DBX_Dialogue.running = true;
try
for (int i = 0; i < line.text.Length; i++)
Text += line.text[i];
await Task.Delay(30, token);
catch (TaskCancelledException)
Text = line.text;
finally
DBX_Dialogue.running = false;
void OnClick(object sender, EventArgs e)
if (DBX_Dialogue.running)
this.tcs.Cancel();
this.tcs.Dispose();
this.tcs = null;
return;
else
this.tcs = new CancellationTokenSource();
_ = Run(tcs.Token);
没有代码示例可以开始,所以我没有费心编译上面的代码,更不用说实际测试了。它可能需要一些摆弄才能让它像你想要的那样工作。但这是基本思想。
【讨论】:
我应该澄清一下,有问题的 OnClick() 属于主窗口,因此在控件执行其操作时它不会被阻止。我会看看并尝试一些替代方案。由于我没有意识到问题出在 Thread.sleep() 命令上,我可能把事情复杂化了。 “有问题的 OnClick() 属于主窗口,因此在控件执行其操作时它不会被阻塞” -- 只有一个线程。或者至少,应该只有一个线程。涉及多个控件并不会改变这一点。来自任何控件的BeginInvoke()
将阻塞(当委托运行时)同一线程拥有的所有控件。
那我恐怕不明白为什么用 await Task.Delay(30) 替换 Thread.Sleep(30) 会奏效。
因为Thread.Sleep(30)
会阻塞线程,而await Task.Delay(30)
不会。以上是关于C#:取消 BeginInvoke()的主要内容,如果未能解决你的问题,请参考以下文章
C#中Control的Invoke和BeginInvoke是相对于支线线程
C#定时器中this.begininvoke执行刷新主界面时