简体   繁体   中英

Getting a TaskScheduler/SynchronizationContext to execute on a specific thread

Consider the following code for a 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);
}

... where modelChanged is an event handler for responding to changes in an object model. This code executes on the UI thread and is designed in hopes of having events handled on the UI thread regardless of which thread they are fired from.

However, when this gets run the output is something similar to:

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

My expectation would be that thread 1 would be where all handling would take place. Even when I try using the SynchronizationContext directly like this:

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);
}

... I see the same thing.

Is there something wrong with my thinking or approach? How do I get the events to be handled on the init thread?

Thanks in advance!

Interesting, your code works for me. Maybe you left out parts of the code that can explain the problem. Can you post a more complete reproduction of the problem? And specifically, show what you are doing with the modelChanged member other than assigning the lambda to it.

What I did was, create an empty WPF application and run your Init method from the main window's constructor.

Then I started background threads that called the modelChanged delegate directly.

What I saw was that the line "ModelChanged on thread..." always printed the correct thread, the one which called Init .

If it's any help, here's what I did to try to reproduce it, you can look at it and maybe post about what you're doing differently:

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();
    }
}

Looks like in your case Init() isn't running on the UI thread. To make sure something runs on the UI thread, you can use some control's (eg Window 's) Dispatcher property, and use it to run code on the UI thread like so:

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

This specific overload of Invoke() is an extension method that requires a reference to System.Windows.Presentation.dll and a using System.Windows.Threading; directive, and requires .NET 3.5 SP1 and above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM