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.Sleepawait Task.Delay 或使用 WinForms 计时器,您仍然不会获得响应行为。看看您的代码。如果有人在您上次投票后立即设置DBX_Dialogue.cancel,则在您看到它之前将是 30 秒。考虑使投票时间更快(例如 250 毫秒)并循环 4x30 次以标记您的 30 秒,或创建更多复杂的机制(可能使用WaitHandle.WaitAnyTask.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()的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 BeginInvoke C#

C# 多线程 Invoke BeginInvoke

C#中Control的Invoke和BeginInvoke是相对于支线线程

C#定时器中this.begininvoke执行刷新主界面时

c#的BeginInvoke和EndInvoke使用demo

C#如何使用异步编程BeginInvoke/EndInvoke