从另一个线程调用方法而不阻塞线程(或为非UI线程编写自定义SynchronizationContext)C#

时间:2016-02-19 08:35:32

标签: c# multithreading thread-synchronization

这可能是Stackoverflow中最常见的问题之一,但是我无法找到问题的确切答案: 我想设计一个模式,允许从线程A启动线程B并在特定条件下(例如,当发生异常时)调用线程A中的方法。如果异常,正确的线程很重要,因为异常必须调用主线程A中的一个catch方法。如果一个线程A是一个UI线程,那么一切都很简单(调用.Invoke().BeginInvoke(),那就是它)。 UI线程有一些机制如何完成,我想获得一些见解如何为非UI线程编写自己的机制。通常建议的方法是使用消息泵http://www.codeproject.com/Articles/32113/Understanding-SynchronizationContext-Part-II
但是while循环会阻塞线程A,这不是我需要的,而不是UI线程处理这个问题的方式。有多种方法可以解决此问题,但我希望能够更深入地了解该问题并编写自己的通用实用程序,而不依赖于所选方法,例如使用System.Threading.ThreadSystem.Threading.Tasks.Task或{{1}或者其他任何东西,如果有或没有UI线程(例如控制台应用程序),则独立
下面是示例代码,我尝试用它来测试异常的捕获(这清楚地表明错误的线程抛出异常)。我将它用作具有所有锁定功能的实用程序,检查线程是否正在运行等等,这就是我创建类实例的原因。

BackgroundWorker

我发现的一个有趣的功能是,class Program { static void Main(string[] args) { CustomThreads t = new CustomThreads(); try { // finally is called after the first action t.RunCustomTask(ForceException, ThrowException); // Runs the ForceException and in a catch calls the ThrowException // finally is never reached due to the unhandled Exception t.RunCustomThread(ForceException, ThrowException); } catch (Exception ex) { Console.WriteLine(ex.Message); } // well, this is a lie but it is just an indication that thread B was called Console.WriteLine("DONE, press any key"); Console.ReadKey(); } private static void ThrowException(Exception ex) { throw new Exception(ex.Message, ex); } static void ForceException() { throw new Exception("Exception thrown"); } } public class CustomThreads { public void RunCustomTask(Action action, Action<Exception> action_on_exception) { Task.Factory.StartNew(() => PerformAction(action, action_on_exception)); } public void RunCustomThread(Action action, Action<Exception> action_on_exception) { new Thread(() => PerformAction(action, action_on_exception)).Start(); } private void PerformAction(Action action, Action<Exception> action_on_exception) { try { action(); } catch (Exception ex) { action_on_exception.Invoke(ex); } finally { Console.WriteLine("Finally is called"); } } } 会抛出未处理的异常,new Thread()从未被调用,而finally则没有,new Task()被调用。也许有人可以评论这种差异的原因。

1 个答案:

答案 0 :(得分:3)

  

而不是UI线程处理此问题的方式

这不准确,它是完全 UI线程如何处理它。消息循环是producer-consumer problem的通用解决方案。在典型的Windows程序中,操作系统以及其他进程会生成消息,并且只使用一个唯一的UI线程。

这种模式需要处理从根本上说是线程不安全的代码。并且总是存在许多不安全的代码,它越复杂,它就越可能成为线程安全的。你可以在.NET中看到的东西,很少有类通过设计是线程安全的。一个简单的东西是List&lt;&gt;不是线程安全的,您可以使用 lock 关键字来保证其安全。 GUI代码非常不安全,没有任何锁定可以使其安全。

不仅因为很难找出放置 lock 语句的位置,还有一堆代码涉及到你没有编写。就像消息钩子,UI自动化,将对象放在剪贴板上的程序,当你使用像OpenFileDialog这样的shell对话框时,你粘贴,拖放,shell扩展。所有这些代码都是线程不安全的,主要是因为它的作者没有拥有来使其成为线程安全的。如果您在此类代码中查找了线程错误,那么您没有可以拨打的电话号码以及完全无法解决的问题。

特定的线程上运行方法调用需要这种帮助。不可能随意中断线程,强制它调用方法。这导致了可怕的,完全无法解决的重新入侵问题。就像DoEvents()引起的那种问题,但乘以一千。当代码进入调度程序循环时,它隐式“空闲”并且不忙于执行自己的代码。因此可以从消息队列中获取执行请求。这仍然可能出错,当你空闲时,你会在你抽水时射击你的腿。这就是DoEvents()如此危险的原因。

所以这里没有捷径,你真的需要处理while()循环。有可能这样做是非常可靠的证明,UI线程做得非常好。考虑creating your own