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

call method from another thread without blocking the thread (or write custom SynchronizationContext for non-UI thread) C#

这可能是 Whosebug 中最常见的问题之一,但是我找不到我的问题的确切答案: 我想设计一个模式,它允许从线程 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.TaskBackgroundWorker 或其他任何独立的东西,如果有 UI 线程(例如控制台应用程序)。

下面是示例代码,我尝试用它来测试异常的捕获(它清楚地表明错误的线程抛出了异常)。我将把它用作具有所有锁定功能的实用程序,检查线程是否为 运行,等等。这就是我创建 class.

实例的原因
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() 不会,并且 finally 被调用.也许有人可以评论这种差异的原因。

and not the way how UI thread handles this issue

这不准确,正是 UI 线程如何处理它。消息循环是producer-consumer problem的通用解决方案。在典型的 Windows 程序中,操作系统和其他进程产生消息,并且唯一的 UI 线程消耗。

需要此模式来处理基本上线程不安全的代码。而且周围总是有很多不安全的代码,代码越复杂,线程安全的可能性就越低。您可以在 .NET 中看到,很少有 类 设计为线程安全的。简单的事情是 List<> 不是线程安全的,您可以使用 lock 关键字来保证它的安全。 GUI 代码非常不安全,再多的锁定也无法使其安全。

不仅仅是因为很难弄清楚 lock 语句放在哪里,还有一堆代码不是你写的。像消息挂钩、UI 自动化、将对象放置在您粘贴、拖放的剪贴板上的程序、shell 当您使用像 OpenFileDialog 这样的 shell 对话框时 运行 的扩展.所有这些代码都是线程不安全的,主要是因为其作者没有 使其成为线程安全的。如果您在此类代码中遇到线程错误,那么您就没有 phone 号码可以拨打,这将是一个完全无法解决的问题。

特定线程上进行方法调用运行需要这种帮助。无论线程正在做什么,都不可能任意中断线程并强制它调用方法。这会导致可怕且完全无法调试的重入问题。像那种由DoEvents()引起的问题,但是乘以一千。当代码进入调度程序循环时,它隐式 "idle" 而不是忙于执行自己的代码。所以可以从消息队列中取出一个执行请求。这仍然可能出错,当你在 not 空闲时抽水时,你会把腿打断。这就是 DoEvents() 如此危险的原因。

所以这里没有捷径,您确实需要处理 while() 循环。这样做是可能的,这是您有充分证据证明的事情,UI 线程做得很好。考虑 creating your own.