Pingpong
Pingpong

Reputation: 8009

TaskScheduler.FromCurrentSynchronizationContext() in .NET

I am getting a runtime exception trying to run the example below.

Unhandled Exception: System.InvalidOperationException: The current SynchronizationContext may not be used as a TaskScheduler.
   at System.Threading.Tasks.SynchronizationContextTaskScheduler..ctor()
   at System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()
   at TaskDemo.MyForm..ctor() in D:\myStudio\ASPNet\CSharp\CSharp4\MyApp\MyApp\Hello.cs:line 428
   at TaskDemo.SynchronizationContextTaskScheduler() in D:\myStudio\ASPNet\CSharp\CSharp4\MyApp\MyApp\Hello.cs:line 396
   at TaskDemo.Go() in D:\myStudio\ASPNet\CSharp\CSharp4\MyApp\CLRviaCSharp\Hello.cs:line 214
   at ComputeOps.Main() in D:\myStudio\ASPNet\CSharp\CSharp4\MyApp\CLRviaCSharp\Hello.cs:line 23

Code sample:

public class TaskSchedulerTest {

    public void Test() {
        SynchronizationContextTaskScheduler();
    }

    private void SynchronizationContextTaskScheduler() {
        var f = new MyForm();
        System.Windows.Forms.Application.Run();
    }

    private sealed class MyForm : System.Windows.Forms.Form {
        public MyForm() {
            Text = "Synchronization Context Task Scheduler Demo";
            Visible = true; Width = 400; Height = 100;
        }

        private readonly TaskScheduler m_syncContextTaskScheduler =
           TaskScheduler.FromCurrentSynchronizationContext();

        private CancellationTokenSource m_cts;

        protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
            if (m_cts != null) {    // An operation is in flight, cancel it
                m_cts.Cancel();
                m_cts = null;
            } else {                // An operation is not in flight, start it
                Text = "Operation running";
                m_cts = new CancellationTokenSource();

                // This task uses the default task scheduler and executes on a thread pool thread
                var t = new Task<Int32>(() => Sum(m_cts.Token, 20000), m_cts.Token);
                t.Start();

                // These tasks use the synchronization context task scheduler and execute on the GUI thread
                t.ContinueWith(task => Text = "Result: " + task.Result,
                   CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
                   m_syncContextTaskScheduler);

                t.ContinueWith(task => Text = "Operation canceled",
                   CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
                   m_syncContextTaskScheduler);

                t.ContinueWith(task => Text = "Operation faulted",
                   CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
                   m_syncContextTaskScheduler);
            }
            base.OnMouseClick(e);
        }
    }
}

Any idea?

Upvotes: 8

Views: 5707

Answers (2)

Alex
Alex

Reputation: 8116

put the creation of m_syncContextTaskScheduler into your form constructor .

public MyForm() {
    Text = "Synchronization Context Task Scheduler Demo";
    Visible = true; Width = 400; Height = 100;
    m_syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}

Upvotes: 10

Hans Passant
Hans Passant

Reputation: 941217

Thanks! It works, what is the reason behind it?

Because the constructor initializes the readonly members that have an initializer too soon. The Form class constructor installs the synchronization provider if needed, an instance of a class named WindowsFormsSynchronizationContext. The C# compiler generates the code for readonly initializers before calling the base class constructor. Moving the assignment into the constructor body ensures that it is initialized after calling the base constructor.

Be careful with readonly member initializers, keep them simple.

Upvotes: 8

Related Questions