Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task ContinueWith causing cross-thread exception despite UI context

I was under the impression that using Task's ContinueWith method with a UI context allows performing operations on UI elements without causing a cross-thread exception. However, I am still getting an exception when running the following code:

var context = TaskScheduler.FromCurrentSynchronizationContext();

Task<SomeResultClass>.Factory.StartNew(SomeWorkMethod).ContinueWith((t) =>
   {
      myListControl.Add(t.Result); // <-- this causes an exception
   }, context);

Any ideas?

like image 499
Amberite Avatar asked Dec 09 '22 06:12

Amberite


2 Answers

There are two different causes of cross-thread exceptions.

The most common one is that you try to modify the state of a control from the non-UI thread. And this is not the issue you're hitting.

The one you're hitting is that controls must be created on the UI thread. Your task is creating the control on a different thread and when you try to add this control to controls created on the UI thread you get the exception.

You need to separate out the work from the control creation to make this work. Try returning a Func<Control> rather than a Control and invoke this on the UI thread before adding it. Keep most of the work on the task thread but form a nice tight closure by returning the Func<> that just does the control creation.

like image 170
Enigmativity Avatar answered Dec 11 '22 19:12

Enigmativity


Aside from the reasons and possible solutions Enigmativity told allready you can allways do something like this:

var context = TaskScheduler.FromCurrentSynchronizationContext();

Task<SomeResultClass>.Factory.StartNew(SomeWorkMethod).ContinueWith((t) =>
   {
      if (!myListControl.InvokeRequired)
         myListControl.Add(t.Result); // <-- this causes an exception
      else
         myListControl.Invoke((Action)(() => myListControl.Add(t.Result)));
   }, context);

(assuming this is WinForms)

if you want mor control refactor the add into a method and use the InvokeRequired inside the method to call itself inside the Invoke if needed:

private void AddToListControl(MyItem item)
{
   if (myListControl.InvokeRequired) 
   {
      myListControl.Invoke((Action)(() => AddToListControl(item)));
      return;
   }

   myListControl.Add(item);
}

What Enigmativity was hinting at is something like this:

var result =
   Task<Action>.Factory.StartNew(SomeWorkMethod).ContinueWith((t) =>
      {
         return () => myListControl.Add(t.Result);
      });

result.Result();

But IMHO this is just the same place you got from the start because you have to call the Result-Action on the right thread yet again.

like image 25
Random Dev Avatar answered Dec 11 '22 21:12

Random Dev