【问题标题】:how to execute a block of code atomically in C#如何在 C# 中原子地执行代码块
【发布时间】:2021-12-29 05:48:35
【问题描述】:

我有一个启动多个后台线程的多线程 UI 应用程序。许多这些线程执行如下代码:

public void Update(){
   if(Dispatcher.HasShutdownStarted()) return;
   Dispatcher.Invoke(()=>{...});
...
}

那我有时可能有一个线程执行下面的代码

pubic void Shutdown(){
   if(Dispatcher.HasShutdownStarted()) return;
   Dispatcher.InvokeShutdown();
}

问题是有时一个线程在另一个线程执行Dispatcher.HasShutdwonStarted() 之后执行Dispatcher.InvokeShutdown(),但在它到达Dispatcher.Invoke(()=>{...}) 之前。这意味着,一旦 Dispatcher 开始关闭,就会有一个线程尝试在 Dispatcher 上执行 lambda。那就是我遇到异常的时候。什么是最好的解决方案?

【问题讨论】:

  • “那是我得到异常的时候。”比如哪些异常?
  • System.Threading.Tasks.TaskCanceledException

标签: c# multithreading atomic dispatcher


【解决方案1】:

您面临的问题是在执行 Invoke 中的代码之前检查了 HasShutdownStarted(因为它在调度程序上排队)

我认为更好的方法是在调用内部检查它,这样你就不需要任何锁了。

public void Update(){
   Dispatcher.Invoke(()=>
   {
       if(Dispatcher.HasShutdownStarted()) return;
       ...
   });
}

【讨论】:

  • 如果你从 UI 线程调用 invoke 它工作得很好。它不会死锁。它只会调用回调而不是将其分派到消息循环。
  • 就像它试图解压一样。如果您关闭窗口并处理(例如)一个套接字,该套接字在他的线程上调用一个断开连接的事件(try/catch),您将遇到死锁。 UIthread => WindowClose => SocketThread => OnDisconnected => UiThread Invoke。
  • UI 线程没有阻塞任何通过。它只是运行代码而不等待任何东西,所以没有死锁。好吧,这是假设您没有做非常糟糕的事情,例如在调用这样的方法之前替换 UI 线程上的当前同步上下文,以便代码在 UI 线程上但没有意识到它是在 UI 线程上,但只是......不要那样做。如果你这样做了,那就是要解决的问题,而不是到处使用 Invoke 来预期人们会做他们不应该做的事情。
  • 你可能不明白我的意思,这只会让你感到困惑。关键是,如果 UI 线程尝试终止某个其他线程 (thread.join) 而其他线程使用 Dispatcher.Invoke,您将遇到死锁。如果这仍然不清楚,我将删除该示例。 :'(
  • 当然,如果这里的关键部分正在等待另一个线程,那么这可能会导致死锁。是什么让你有任何迹象表明它正在这样做?这听起来像是在这个关键部分没有业务的事情。让 UI 线程阻塞直到某个其他线程完成听起来是个坏主意,即使该其他线程从未分派给 UI 线程。不应从 UI 线程运行长时间运行的操作,例如等待任意工作线程完成。这应该是异步的。
【解决方案2】:

在其他人的帮助下,我设法为我的问题提出了以下解决方案,并认为我会分享它。在Dispatcher.InvokeShutdown() 之后调用Dispatcher.Invoke(...) 总是会导致TaskCancelationException 被抛出(据我所知)。因此,在 Invoke 方法中检查 Dispatcher.HasShutdownStarted 将不起作用。

我所做的是通过创建static CancellationTokenSource 来创建应用程序全局CancellationToken。我现在按如下方式调用 Dispatcher:

Dispatcher.Invoke(()=>{...}, DispatcherPriority.Send, GlobalMembers.CancellationTokenSource.Token);

然后,当我希望在调度程序上调用关闭时,我执行以下操作:

GlobalMembers.CancellationTokenSource.Cancel();
Dispatcher.InvokeShutdown();

如果我在取消全局令牌并调用 Dispatcher.InvokeShutdown() 之后尝试运行 Dispatcher.Invoke(()=>{...}, DispatcherPriority.Send, GlobalMembers.CancellationTokenSource.Token),则不会发生任何事情,因为令牌已被取消,因此操作不会运行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 2018-12-21
    • 2017-05-29
    • 2015-03-14
    相关资源
    最近更新 更多