#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 и выше.