0

In my wpf application I know I should update UI elements on the main thread. What I do is I use the main window dispatcher in order to do so. I am just curios to see why this code does not work:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += MainWindow_Loaded;            
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(() =>
        {
            Console.Write("test");

            Task.Factory.StartNew(() =>
            {
                // why does this code does not execute!! ???
                Thread.Sleep(1000);
                txt.Text = "Testing";
            }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());

        });                      
    }
}

why does the inner task does not execute? In other words my program never reaches the line Thread.Sleep(1000); Why is this?

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • are you sure it's not executed? The problem might be here `txt.Text = "Testing";` this won't work as you are not on the UI thread. Replace it with a Write or make a break point to verify – thumbmunkeys Sep 20 '14 at 16:28
  • I never hit the breakpoint yes I am sure it does not execute. I am just asking this question to learn more about tasks... – Tono Nam Sep 20 '14 at 16:44
  • possible duplicate of [update UI in Task using TaskScheduler.FromCurrentSynchronizationContext](http://stackoverflow.com/questions/17418208/update-ui-in-task-using-taskscheduler-fromcurrentsynchronizationcontext) – thumbmunkeys Sep 20 '14 at 17:15

2 Answers2

3

It is possible that TaskScheduler.FromCurrentSynchronizationContext() will throw an InvalidOperationException when there is no synchronization context on the calling thread i.e. SynchronizationContext.Current returns null.

So, in order to catch UI TaskScheduler, you should get it earlier:

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() =>
    {
        Console.Write("test");

        Task.Factory.StartNew(() =>
        {
            Thread.Sleep(1000);
            txt.Text = "Testing";
        }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);

    });                      
}

You also can use continuation tasks, and run it on the thread you need:

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() =>
    {
        Console.Write("test");
    }).ContinueWith(() =>
        {
            Thread.Sleep(1000);
            txt.Text = "Testing";
        }, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
stukselbax
  • 5,855
  • 3
  • 32
  • 54
0

You can use the dispatcher directly:

public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Loaded += MainWindow_Loaded;
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
      Task.Factory.StartNew(() =>
      {
        Console.Write("test");
        Thread.Sleep(1000);

        Application.Current.Dispatcher.BeginInvoke(
          DispatcherPriority.Normal, new Action(() => 
          {
            txt.Text = "Testing";
          }));
      });
    }
  }
Dean
  • 731
  • 8
  • 21