获取TaskScheduler / SynchronizationContext以在特定线程上执行

时间:2011-10-18 21:37:48

标签: c# scheduled-tasks synchronizationcontext

考虑WPF ViewModel的以下代码:

protected void Init()
{
    Debug.WriteLine(string.Format("ChangeManager init on thread={0}", Thread.CurrentThread.ManagedThreadId));          

    var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    this.modelChanged = (o, args) => Task.Factory.StartNew(() =>
        {                   
            Debug.WriteLine(string.Format("ModelChanged on thread={0}", Thread.CurrentThread.ManagedThreadId));
            this.ModelChanged(o, args);
        },
        CancellationToken.None,
        TaskCreationOptions.None,
        uiTaskScheduler);
}

...其中modelChanged是用于响应对象模型中的更改的事件处理程序。此代码在UI线程上执行,其设计希望在UI线程上处理事件,而不管它们从哪个线程触发。

然而,当它运行时,输出类似于:

ChangeManager init on thread=1
ModelChanged on thread=3
ModelChanged on thread=3
ModelChanged on thread=7
ModelChanged on thread=9

我的期望是线程1将是所有处理将发生的地方。即使我尝试直接使用SynchronizationContext,如下所示:

protected void Init()
{
    Debug.WriteLine(string.Format("ChangeManager init on thread={0}", Thread.CurrentThread.ManagedThreadId));          

    this.uiContext = SynchronizationContext.Current;        

    modelChanged = (o, args) => uiContext.Post((ignore) => {
        Debug.WriteLine(string.Format("ModelChanged on thread={0}", Thread.CurrentThread.ManagedThreadId));
        this.ModelChanged(o, args);
    }
    , null);
}

......我看到同样的事情。

我的想法或方法有问题吗?如何在init线程上处理事件?

提前致谢!

2 个答案:

答案 0 :(得分:2)

有趣的是,您的代码适合我。也许您遗漏了可以解释问题的部分代码。你能发布一个更完整的问题再现吗?具体而言,除了将lambda分配给它之外,还要显示您使用modelChanged成员所做的事情。

我做的是,创建一个空的WPF应用程序并从主窗口的构造函数中运行Init方法。

然后我开始直接调用modelChanged委托的后台线程。

我看到的是“线程上的ModelChanged ......”行总是打印出正确的线程,即调用Init的线程。

如果有任何帮助,这就是我尝试重现它的方法,您可以查看它,也许可以发布您正在做的不同的事情:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Init();
    }

    private EventHandler modelChanged;

    protected void Init()
    {
        Trace.WriteLine(string.Format("ChangeManager init on thread={0}",
                 Thread.CurrentThread.ManagedThreadId));

        var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

        modelChanged = (o, args) => Task.Factory.StartNew(() =>
        {
            Trace.WriteLine(string.Format("ModelChanged on thread={0}", 
                Thread.CurrentThread.ManagedThreadId));

            if (ModelChanged != null)
            {
                ModelChanged(o, args);
            }
        },
            CancellationToken.None,
            TaskCreationOptions.None,
            uiTaskScheduler);
    }

    public event EventHandler ModelChanged;

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var t = new Thread(
            obj =>
                 {
                      Trace.WriteLine(string.Format(
                          "Launching handler on thread={0}", 
                          Thread.CurrentThread.ManagedThreadId));

                      modelChanged(null, EventArgs.Empty);
                 });
        t.Start();
    }
}

答案 1 :(得分:0)

在您的情况下看起来Init()未在UI线程上运行。要确保某些内容在UI线程上运行,您可以使用某些控件(例如Window')Dispatcher属性,并使用它在UI线程上运行代码,如下所示:

someControl.Dispatcher.Invoke(() => { /* do something with the UI */ });

Invoke()的特定重载是一种扩展方法,需要引用System.Windows.Presentation.dllusing System.Windows.Threading;指令,并且需要.NET 3.5 SP1及更高版本。