Dispatcher.BeginInvoke正确用法?

时间:2014-01-07 19:11:44

标签: c# multithreading begininvoke

我正在努力了解如何正确使用BeginInvoke。我在一个控制台应用程序中编写了一个小测试,其中我要做的就是使用BeginInvoke调用一个函数来制作一个标题弹出的100x100窗口。我悲惨地失败了。这就是我所拥有的,我知道这可能只是对Threads的理解不够(不是我强大的西装),但是我被卡住了,没有窗口弹出我只是在主要等待按键的读取线上结束。执行从ThreadUITest开始。

static void ThreadUITest()
{
    ThreadStart starter = new ThreadStart(threadFunc1);
    Thread test = new Thread(starter);
    test.IsBackground = true;
    test.SetApartmentState(ApartmentState.STA);
    test.Start();
}

static void threadFunc1()
{
    dispatcher = Dispatcher.CurrentDispatcher; //Statically declared earlier
    ThreadStart starter = new ThreadStart(threadFunc2);
    Thread test = new Thread(starter);
    test.IsBackground = true;
    test.Start();
}

static void threadFunc2()
{
    Action method = Draw;
    Console.WriteLine("I'm here!");
    //dispatcher.BeginInvoke( (Action)(() => {Draw();}),DispatcherPriority.Render, null);
    dispatcher.BeginInvoke(method, DispatcherPriority.Send, null);
 }

 static void Draw()
 {
     Window win = new Window();
     win.Height = 100;
     win.Width = 100;
     win.Title = "A Window!";
     win.Show();
 }

感谢您的帮助。

2 个答案:

答案 0 :(得分:2)

您需要在threadFunc1

的底部添加以下内容
// statically declared earlier, although you don't need to keep a reference to it as 
// WPF will keep it in Application.Current
application = new Application(); 
application.Run(); // thread1 is now our "UI" thread

为什么要解决这个问题?

Dispatcher对象提供了一个界面,用于让线程为您做一些工作(通过BeginInvokeInvoke)。
为了让一个线程能够处理任何“do work”消息,它必须运行某种事件循环,它位于并等待下一条消息要处理 - 如果它没有这样做,那么它就不会不能处理任何东西,它会被卡住。

从thread1调用Dispatcher.CurrentDispatcher将在该线程上创建一个新的调度程序(如果还没有那个[1]) - 它为我们提供了向线程发送消息的接口。
dispatcher.BeginInvoke所做的是为该线程的消息队列添加一个条目,但该线程尚未运行任何消息循环。我们可以将消息排队到它,但它不会接收它们并运行它们 - 这就是没有任何反应的原因。

因此,我们需要让该线程开始运行消息循环 Application.Run()方法是WPF框架方法,它正是这样做的。 Application.Run方法永远不会返回(无论如何都要调用Application.Shutdown),它会启动一个消息循环,以便此后开始处理消息。我觉得把它“接管”线程是有用的。

现在有了这个改变,当thread2func调用dispatcher.BeginInvoke时,Application.Run内的消息循环代码变为“哦,看一条消息,我会处理它” - 它得到BeginInvoke方法,并做了它告诉的事情(在这种情况下,执行你的Draw功能),一切都很好


注意:根据Ark-kun's answer您也可以调用Dispatcher.Run在该线程上启动消息循环而不创建Application对象(Application.Run在内部执行此操作)。通常我觉得创建一个应用程序对象更好,因为它更“正常”,稍后您可能会写的其他一些代码可能期望Application对象存在


[1]仅供参考,这就是为什么调用Dispatcher.CurrentDispatcher是危险的,你应该避免它。如果从现有UI线程中调用Dispatcher.CurrentDispatcher,则会返回对正确调度程序的引用 如果你不小心从另一个线程中调用它,从逻辑上讲,你会认为它将返回对现有调度程序的引用。但是没有 - 相反,它会创建一个第二个调度程序,指向我们的其他线程 - 但是我们的其他线程将不会运行消息循环,我们将再次卡住。我建议除了第一次以外从不打电话给Dispatcher.CurrentDispatcher

一旦您的应用程序启动并运行,您通常无需执行此操作,因为所有WPF对象(Window,Button等)都具有Dispatcher属性,您可以使用该属性来获取正确的无论如何,调度员

答案 1 :(得分:1)

尝试在Dispatcher.Run()的末尾调用threadFunc1