Обновить пользовательский интерфейс из потока в WinRT
Поскольку предварительный просмотр Windows 8 был выпущен несколько дней назад, я работаю над новым WinRT (для приложений Metro) на С#, и я портировал свой собственный IRC-класс на новую потоковую передачу и сетевое взаимодействие.
Проблема: в моем классе работает поток для получения сообщений с сервера. Если это происходит, поток выполняет некоторый синтаксический анализ, а затем запускает событие, чтобы сообщить об этом программе. Подписанная функция затем "должна" обновлять пользовательский интерфейс (текстовый блок).
Это проблема, нить не может обновить интерфейс, а метод invoker, который работал с .NET 4.0, кажется, невозможен. Существует ли новое решение для этого или даже лучшего способа обновления пользовательского интерфейса? Если я попытаюсь обновить пользовательский интерфейс от подписчика событий, я получу этот Exception
:
Приложение называется интерфейсом, который был другой поток (исключение из HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
Ответы
Ответ 1
Предпочтительным способом справиться с этим в WinRT (и С# 5 в целом) является использование async
- await
:
private async void Button_Click(object sender, RoutedEventArgs e)
{
string text = await Task.Run(() => Compute());
this.TextBlock.Text = text;
}
Здесь метод Compute()
будет работать в фоновом потоке, а когда он закончится, остальная часть метода будет выполняться в потоке пользовательского интерфейса. Тем временем поток пользовательского интерфейса может делать все, что ему нужно (например, обрабатывать другие события).
Но если вы не хотите или не можете использовать async
, вы можете использовать Dispatcher
аналогичным способом (хотя и другим), как в WPF:
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() => Compute());
}
private void Compute()
{
// perform computation here
Dispatcher.Invoke(CoreDispatcherPriority.Normal, ShowText, this, resultString);
}
private void ShowText(object sender, InvokedHandlerArgs e)
{
this.TextBlock.Text = (string)e.Context;
}
Ответ 2
Вот более простой способ сделать это, я думаю!
Сначала запишите свой интерфейс SynconizationContext интерфейса пользователя следующим образом:
var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();
Запустите операцию вызова сервера или любую другую операцию фонового потока, которая вам нужна:
Task serverTask= Task.Run(()=> { /* DoWorkHere(); */} );
Затем выполните операцию UI в UISyncContext, который вы перенесли на первый шаг:
Task uiTask= serverTask.ContinueWith((t)=>{TextBlockName.Text="your value"; }, UISyncContext);
Ответ 3
IMO Я думаю, что рекомендуемый маршрут "ThreadPool".
https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh465290.aspx
public static Task InvokeBackground(Func<Task> action)
{
var tcs = new TaskCompletionSource<bool>();
var unused = ThreadPool.RunAsync(async (obj) =>
{
await action();
tcs.TrySetResult(true);
});
return tcs.Task;
}