Получение TaskScheduler / SynchronizationContext для выполнения в определенном потоке

#c# #scheduled-tasks #synchronizationcontext

#c# #запланированные задачи #synchronizationcontext

Вопрос:

Рассмотрим следующий код для модели представления WPF:

 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 — это обработчик событий для реагирования на изменения в объектной модели. Этот код выполняется в потоке пользовательского интерфейса и разработан в надежде на обработку событий в потоке пользовательского интерфейса независимо от того, из какого потока они запускаются.

Однако, когда это запускается, результат выглядит примерно так:

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

… Я вижу то же самое.

Что-то не так с моим мышлением или подходом? Как мне заставить события обрабатываться в потоке инициализации?

Заранее спасибо!

Ответ №1:

Интересно, ваш код работает для меня. Возможно, вы пропустили части кода, которые могут объяснить проблему. Можете ли вы опубликовать более полное воспроизведение проблемы? И, в частности, покажите, что вы делаете с modelChanged элементом, кроме присвоения ему лямбда.

Что я сделал, так это создал пустое приложение WPF и запустил ваш метод инициализации из конструктора главного окна.

Затем я запустил фоновые потоки, которые вызывали 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();
    }
}
  

Ответ №2:

Похоже, в вашем случае Init() он не выполняется в потоке пользовательского интерфейса. Чтобы убедиться, что что-то выполняется в потоке пользовательского интерфейса, вы можете использовать свойство некоторого элемента управления (например Window , ‘s) Dispatcher и использовать его для запуска кода в потоке пользовательского интерфейса следующим образом:

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

Эта конкретная перегрузка Invoke() является методом расширения, который требует ссылки System.Windows.Presentation.dll и using System.Windows.Threading; директивы и требует .NET 3.5 SP1 и выше.