在主线程中运行代码

9

这个问题和许多其他问题类似,但又不一样。我需要像 BeginInvoke 这样的方法来处理 Winforms,但不仅限于 Winforms。因此,我需要一个适用于任何应用程序类型的单一方法,因此我正在调用。

void ExecuteInMainContext(Action action)
{
   ...
}

希望这个方法可以在控制台、WinForms、WPF等应用中调用,并且不会出现问题。我看到的所有方法都是使用 BeginInvoke 在 WinForms 上,使用 Dispatcher.Invoke 在 WPF 上等等。但是我需要从库中调用它,并且我不知道从哪里调用它。同时,对于调用代码来说,它也应该是透明的,因此不应该传递像指向调用主线程的指针之类的东西,库应该从环境本身获取这些信息,而不是从用户代码或任何全局变量中获取。

我尝试使用 Task.ConfigureAwait,但它并没有帮助。

我找到了这篇文章:

在控制台应用程序中,除非经过大量工作,否则无法完成此操作。TPL内置的机制用于将回调调度到线程上,这可能取决于线程是否安装了 SynchronizationContext。这通常由用户界面框架(即:Windows Forms 中的 Application.Run,或者在 WPF 的启动代码中等)安装。

但我希望这是可能的。

下面是测试代码:

using System;
using System.Threading;

namespace Test
{
    class Program
    {
        private static void Main(string[] args)
        {

            Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
            Publisher publisher = new Publisher(Method);
            Console.ReadLine();
        }

        private static void Method(string s)
        {
            Console.WriteLine(s + " " + Thread.CurrentThread.ManagedThreadId);
        }

    }

    class Publisher
    {
        public event Action<string> Action;

        protected virtual void OnAction(string obj)
        {
            Action<string> handler = Action;
            if (handler != null)
            {
                SafeCall(() => handler(obj));
            }
        }

        private static void SafeCall(Action action)
        {
            // ???
            action(); // should write 1
        }

        public Publisher(Action<string> action)
        {
            Action = action;
            Console.WriteLine("Publisher thread: " + Thread.CurrentThread.ManagedThreadId);
            Thread thread = new Thread(() => OnAction("hello"));
            thread.Start();
        }
    }
}

所以它应该在任何地方写入相同的数字。

请查看SynchronizationContext这里 - Alberto
所以在我的情况下它是空的。 - Alex Zhukovskiy
1个回答

10

试试这个

void ExecuteInMainContext(Action action)
    {
        var synchronization = SynchronizationContext.Current;
        if (synchronization != null)
        {
            synchronization.Send(_ => action(), null);//sync
            //OR
            synchronization.Post(_ => action(), null);//async
        }
        else
            Task.Factory.StartNew(action);

        //OR
        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

        Task task = new Task(action);
        if (scheduler != null)
            task.Start(scheduler);
        else
            task.Start();
    }

task.Start(TaskScheduler.FromCurrentSynchronizationContext()); 抛出异常SynchronizationContext.Current 为空。 - Alex Zhukovskiy
1
对于 ConsoleApplication,它将为 null。对于这种情况,请使用简单的 Task.Factory.StartNew(action)。 - yo chauhan
代码在上面。在我的情况下,它写入了“4”,而应该写入“1”。或者这是否意味着,在处理控制台时我们根本不应该使用任何同步? - Alex Zhukovskiy
是的,因为对于控制台应用程序,任务将被安排到线程池中的任何可用线程,但对于WPF和Winform,如果线程的托管ID为1,则只有一个线程。 - yo chauhan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接