Using Thread is pretty straightforward
Thread thread = new Thread(MethodWhichRequiresSTA);
thread.SetApartmentState(ApartmentState.STA);
How to accomplish the same using Tasks in a WPF application? Here is some code:
Task.Factory.StartNew
(
() =>
{return "some Text";}
)
.ContinueWith(r => AddControlsToGrid(r.Result));
I'm getting an InvalidOperationException with
The calling thread must be STA, because many UI components require this.
You can use the TaskScheduler.FromCurrentSynchronizationContext Method to get a TaskScheduler for the current synchronization context (which is the WPF dispatcher when you're running a WPF application).
Then use the ContinueWith overload that accepts a TaskScheduler:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(...)
.ContinueWith(r => AddControlsToGrid(r.Result), scheduler);
Dispatcher.Invoke could be a solution. e.g.
private async Task<bool> MyActionAsync()
{
// await for something, then return true or false
}
private void StaContinuation(Task<bool> t)
{
myCheckBox.IsChecked = t.Result;
}
private void MyCaller()
{
MyActionAsync().ContinueWith((t) => Dispatcher.Invoke(() => StaContinuation(t)));
}
Related
I would like to scrape data from one site, so because rapidity is important for my project i must run tasks in parallel. I have a method like this:
public void UpdateData(List<string> myList)
{
while(true)
{
...
...
}
}
And i would like to call the method with different arguments from buttonClick Event so i used this code:
var uiContext = TaskScheduler.FromCurrentSynchronizationContext();
var task1 = Task.Factory.StartNew(() => UpdateData(myList1), CancellationToken.None, TaskCreationOptions.LongRunning, uiContext);
var task2 = Task.Factory.StartNew(() => UpdateData(myList2), CancellationToken.None, TaskCreationOptions.LongRunning, uiContext);
The result is after first calling of tasks only the first one continues to update the argument(myList1).
Where is the problem?
You're explicitly asking the Task Scheduler to run tasks on UI context. There is only one UI context, so only one thread will run at a time.
Perform your tasks on non-UI context
When you need the UI context, marshal the calls as needed
I am using 3 Tasks to execute 3 tasks simultaneously, however, when started all the tasks there is no freezing of the GUI, it only gets a bit slow ... when it returns the result of the last task it totally freezes and stops updating the GUI ...
async Task UpdateBlockChain()
{
var task = Task.Factory.StartNew((Action) =>
{
while (true)
{
BlockChain blockChain = new BlockChain();
coinList[0].Price = blockChain.GetDataByNode("last");
coinList[0].Low = blockChain.GetDataByNode("low");
coinList[0].High = blockChain.GetDataByNode("high");
RefreshView();
Task.Delay(1000);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
await Task.Delay(500);
}
async Task UpdateBitFinex()
{
var task = Task.Factory.StartNew((Action) =>
{
while (true)
{
Bitfinex bitFinex = new Bitfinex();
coinList[1].Price = bitFinex.GetDataByNode("last_price");
coinList[1].Low = bitFinex.GetDataByNode("low");
coinList[1].High = bitFinex.GetDataByNode("high");
RefreshView();
Task.Delay(2000);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
await Task.Delay(500);
}
async Task UpdateBitstamp()
{
var task = Task.Factory.StartNew((Action) =>
{
while (true)
{
Bitstamp bitstamp = new Bitstamp();
coinList[2].Price = bitstamp.GetDataByNode("last");
coinList[2].Low = bitstamp.GetDataByNode("low");
coinList[2].High = bitstamp.GetDataByNode("high");
RefreshView();
Task.Delay(1000);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
await Task.Delay(500);
}
Refresh View:
void RefreshView()
{
if (dataGridView1.InvokeRequired)
{
dataGridView1.BeginInvoke(new Action(() =>
{
dataGridView1.Update();
dataGridView1.Refresh();
}));
}
}
Run task:
await UpdateBlockChain();
await UpdateBitFinex();
await UpdateBitstamp();
Here is a example of class https://pastebin.com/DuQybhcz
I do not know the methods I am using are wrong, I apologize for code flow error.
This is because you use TaskScheduler.FromCurrentSynchronizationContext which is supposed to schedule the tasks to run on the same thread as the calling one, which in your case is UI thread.
Offload all your work to background threads, and only marshal the ui refresh operations to your UI thread by using BeginInvoke
Also you absolutely have to call await Task.Delay(xxx) inside of your while loops, otherwise there are no delays between cycles, and they are very CPU intensive, and schedule too many UI updates
To quote MSDN on TaskScheduler
You can use the TaskScheduler.FromCurrentSynchronizationContext method
to specify that a task should be scheduled to run on a particular
thread. This is useful in frameworks such as Windows Forms and Windows
Presentation Foundation where access to user interface objects is
often restricted to code that is running on the same thread on which
the UI object was created. For more information, see How to: Schedule
Work on the User Interface (UI) Thread.
The following example uses the
TaskScheduler.FromCurrentSynchronizationContext method in a Windows
Presentation Foundation (WPF) app to schedule a task on the same
thread that the user interface (UI) control was created on.
While I was refactoring some old C# code for document generation with the Office.Interop library, I found this and because of it was using UI context. When functions were called from it it was blocking it
For example:
private void btnFooClick(object sender, EventArgs e)
{
bool documentGenerated = chckBox.Checked ? updateDoc() : newDoc();
if(documentGenerated){
//do something
}
}
I decided to change it to reduce from blocking UI:
private async void btnFooClick(object sender, EventArgs e)
{
bool documentGenerated; = chckBox.Checked ? updateDoc() : newDoc();
if(chckBox.Checked)
{
documentGenerated = await Task.Run(() => updateDoc()).ConfigureAwait(false);
}
else
{
documentGenerated = await Task.Run(() => newDoc()).ConfigureAwait(false);
}
if(documentGenerated){
//do something
}
}
It was throwing this error:
Current thread must be set to single thread apartment (STA) mode
before OLE calls can be made
Why does it happen and what is the workaround?
The COM components accessed through Interop require the calling thread to be a STA thread but in your case it is not STA. Otherwise the STA component could be accessed through multiple threads. You can read more about why STA is required in Understanding and Using COM Threading Models.
You can make a extension method on Task class as suggested in Set ApartmentState on a Task to call the COM component through Interop using task:
public static Task<T> StartSTATask<T>(Func<T> func)
{
var tcs = new TaskCompletionSource<T>();
Thread thread = new Thread(() =>
{
try
{
tcs.SetResult(func());
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
When you use Thread instead of task, you have to set the ApartmentState to STA using something like thread.SetApartmentState(ApartmentState.STA).
Because in this case Task presumably starts a new thread that isn't an STA thread. Your calls to updateDoc and newDoc are the ones that call the Interop layer, which doesn't like MTA threads.
You could refactor this to use Thread instead of Task and set the apartment to STA by yourself. I would be careful though, because I am not sure Interop likes multi-threading.
I'm having troubles with the following example:
public void Method()
{
LongRunningMethod();
}
LongRunningMethod() takes around 5 seconds to invoke. I am invoking Method() from the UI thread, so it obviously should freeze the UI. The solution for that is to run Method() within a new Task so I am running it like this:
Task.Factory.StartNew(()=>{Method()})
It's still blocking the UI so I thought whether LongRunningMethod() is using the UI context probably. Then I tried another solution:
new Thread(()=>Method()).Start()
and it started working. How is that possible? I know that Task is not guaranteed to be run on a different thread but CLR should be smart enough to figure out that it's long running method.
You are scheduling work on the User Interface (UI) Thread cause you are using
TaskScheduler.FromCurrentSynchronizationContext()) in this code:
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}
And this is a reason why your UI is frozen. To prevent try to change TaskScheduler to Default:
Task task = Task.Run(() => {; });
Task nextTask = task.ContinueWith(x =>
{
//DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
Task.Factory.StartNew is dangerous cause it uses TaskScheduler.Current as opposed to TaskScheduler.Default. To prevent this use Task.Run which always points to TaskScheduler.Default. Task.Run is new in .NET 4.5, if you're in .NET 4.0 you can create your TaskFactory with default parameters.
As MSDN says:
TaskScheduler.FromCurrentSynchronizationContext()) means schedule
a task on the same thread that the user interface (UI) control was
created on.
Update:
What happens when you run method RunTask():
var task = new Task(action, cancellationTokenSource.Token);
create a "task". (task is not run. The "task" is just queed to the ThreadPool.)
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
create a "nextTask" which will start performing AFTER "task" is completed and the "nextTask" will be performed on UI thread as you've set a feature
TaskScheduler.FromCurrentSynchronizationContext().
task.Start();
You run your "task". When the "task" is completed, then "nextTask" is run by method "task.ContinuuWith()" which will be performed on UI thread you wrote (TaskScheduler.FromCurrentSynchronizationContext()
So to sum up, the two your tasks are interconnected and continuation of task is performed on UI thread which is a reason to freeze your UI. To prevent this behavior use TaskScheduler.Default.
This is exactly how it looks like:
public void RunTask(Action action){
var task = new Task(action, cancellationTokenSource.Token);
Task nextTask = task.ContinueWith(x =>
{
DoSomething();
}, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
}
public void DoSomething()
{
if(condition) // condition is true in this case (it's recurency but not permanent)
RunTask(() => Method()); // method is being passed which blocks UI when invoked in RunTask method
}
public void Method()
{
LongRunningMethod();
}
This is the starting point invocation (UI Thread):
RunTask(()=>Action());
Only a guess: Thread.Start creates a foreground thread. Maybe the method switches to a known foreground-thread when it detects, that it is run from a background-thread.
Hope it helps somehow.
Is there a 'standard' way to specify that a task continuation should run on the thread from which the initial task was created?
Currently I have the code below - it is working but keeping track of the dispatcher and creating a second Action seems like unnecessary overhead.
dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
});
Task UITask= task.ContinueWith(() =>
{
dispatcher.Invoke(new Action(() =>
{
this.TextBlock1.Text = "Complete";
}
});
Call the continuation with TaskScheduler.FromCurrentSynchronizationContext():
Task UITask= task.ContinueWith(() =>
{
this.TextBlock1.Text = "Complete";
}, TaskScheduler.FromCurrentSynchronizationContext());
This is suitable only if the current execution context is on the UI thread.
With async you just do:
await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);
However:
await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.
If you have a return value you need to send to the UI you can use the generic version like this:
This is being called from an MVVM ViewModel in my case.
var updateManifest = Task<ShippingManifest>.Run(() =>
{
Thread.Sleep(5000); // prove it's really working!
// GenerateManifest calls service and returns 'ShippingManifest' object
return GenerateManifest();
})
.ContinueWith(manifest =>
{
// MVVM property
this.ShippingManifest = manifest.Result;
// or if you are not using MVVM...
// txtShippingManifest.Text = manifest.Result.ToString();
System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);
}, TaskScheduler.FromCurrentSynchronizationContext());
I just wanted to add this version because this is such a useful thread and I think this is a very simple implementation. I have used this multiple times in various types if multithreaded application:
Task.Factory.StartNew(() =>
{
DoLongRunningWork();
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{ txt.Text = "Complete"; }));
});
Got here through google because i was looking for a good way to do things on the ui thread after being inside a Task.Run call - Using the following code you can use await to get back to the UI Thread again.
I hope this helps someone.
public static class UI
{
public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}
public struct DispatcherAwaiter : INotifyCompletion
{
public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();
public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);
public void GetResult() { }
public DispatcherAwaiter GetAwaiter()
{
return this;
}
}
Usage:
... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...