17

Since the Windows 8 consumer preview was released a few days ago, I am working on the new WinRT (for Metro Applications) in C# and I had ported my self written IRC class to the new threading and networking.

The problem is: My class is running an thread for receiving messages from the server. If this happens, the thread is making some parsing and then firing an event to inform the application about this. The subscribed function then 'should' update the UI (an textblock).

This is the problem, the thread cannot update the UI and the invoker method that has worked with .NET 4.0 doesn't seem to be possible anymore. Is there an new workaround for this or even an better way to update the UI ? If I try to update the UI from the event subscriber i will get this Exception:

The application called an interface that was marshalled for a different thread (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

Anton Sizikov
  • 9,105
  • 1
  • 28
  • 39
Suchiman
  • 1,636
  • 2
  • 20
  • 30
  • This is by design. Threads cost battery power. The new style for asynchronous IO is by specifying continuations. There is some coverage (tutorial vidoes) on this on http://channel9.msdn.com. –  Mar 02 '12 at 21:03
  • On second thought, It could be that BackgroundWorker still works, which is more like a thread, and it also has marshalling (you can send progress updates to UI thread). –  Mar 02 '12 at 21:04

3 Answers3

28

The preferred way to deal with this in WinRT (and C# 5 in general) is to use async-await:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    string text = await Task.Run(() => Compute());
    this.TextBlock.Text = text;
}

Here, the Compute() method will run on a background thread and when it finishes, the rest of the method will execute on the UI thread. In the meantime, the UI thread is free to do whatever it needs (like processing other events).

But if you don't want to or can't use async, you can use Dispatcher, in a similar (although different) way as in 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;
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • Thank you very much, your second idea has solved it for me without rewriting my whole class :), just some little replacements with your code snippets – Suchiman Mar 02 '12 at 22:14
  • For the second example, I strongly suggest using a lambda instead of a ShowText method - it allows you to avoid the "e.Context". So Dispatcher.Invoke(CoreDispatcherPriority.Normal, (s,e) => { this.TextBlock.Text = resultString}, this, null); – Larry Osterman Mar 04 '12 at 02:44
  • What if the code is not in a page ? How can we get a dispatcher from BG thread ? – Grigory Jun 12 '12 at 14:40
  • @Grigory You could try using `Window.Current.Dispatcher`, but I'm not sure whether that would work. If not, you would have to somehow pass the `Dispatcher` to the method that executes on the BG thread. – svick Jun 12 '12 at 14:57
  • Yeah, Window.Current returns null. Seems that passing dispatcher or SynchronisationContext is the only solution. – Grigory Jun 12 '12 at 15:05
8

Here is an easier way to do it I think!

First capture your UI SyncronizationContext with the following:

    var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

Run your server call operation or any other background thread operation you need:

    Task serverTask= Task.Run(()=> { /* DoWorkHere(); */} );

Then do your UI operation on the UISyncContext you captured in first step:

    Task uiTask= serverTask.ContinueWith((t)=>{TextBlockName.Text="your value"; }, UISyncContext);
Deeb
  • 307
  • 5
  • 6
  • 1
    So, is it true that the WINRT answer to "how do I get the UI context from some random background thread?" seems to not have a simple answer other than modify all of your interfaces to pass in a UI context? The above answer by Deeb seems to work if your background thread object is created from the UI thread (and hence you can capture UISyncContext in the constructor). But what if it's NOT created on the UI thread? What am I missing, and why isn't there a brain dead way to reliably get the UI context from any random background thread? – Jay Borseth Feb 23 '13 at 23:08
  • 1
    Doesn't work for me. I get an InvalidOp exception: `The current SynchronizationContext may not be used as a TaskScheduler.` I'm developing for MS Surface on Win SDK 8 – Howie Aug 21 '13 at 11:57
0

IMO I think "ThreadPool" is the recommended route.

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;
    }
Quincy
  • 1,710
  • 14
  • 20