How to create a task (TPL) running a STA thread?
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);
How to force a task to run on an STA thread?
I have WinForms application, and I want to use the Windows.Forms.OpenFileDialog, which requires an STA thread.
Yes. Every WinForms app has an STA thread which is its main thread. It's easiest to just use that thread.
It works fine with threads, but what about tasks? ... How to create a task (TPL) running a STA thread?
Apartment threading models is a thread concept. There's no such thing as an STA task.
However, a Delegate Task can run on an STA thread if it is scheduled to that thread.
I don't understand how TaskScheduler.FromCurrentSynchronizationContext() is supposed to force a task to run on a STA thread
It doesn't always do that. TaskScheduler.FromCurrentSynchronizationContext
will create a TaskScheduler
that schedules tasks to SynchronizationContext.Current
. Now, on WinForms, if the calling code is on the UI thread, then SynchronizationContext.Current
is a WindowsFormsSynchronizationContext
instance. That WindowsFormsSynchronizationContext
will execute code on the UI thread. So, the TaskScheduler
will schedule tasks to the UI thread (which is an STA thread), and the tasks end up running on the existing STA thread. Again, that is if TaskScheduler.FromCurrentSynchronizationContext
was called from the UI thread in the first place.
If TaskScheduler.FromCurrentSynchronizationContext
is called from a thread pool thread, then SynchronizationContext.Current
is null
, and the resulting TaskScheduler
schedules tasks on a thread pool thread, which is not STA.
So how do I forcibly start a task on STA apartment state, so that I can use components that require it in my application?
The optimum way to do this is to structure your code so that the UI thread calls the background threads, not the other way around. Don't have your background threads call the UI to do things. If the UI is in control, then simpler patterns like await
and IProgress<T>
can be used to coordinate with the background threads. If background thread(s) must drive the UI, then one solution is to capture the UI SynchronizationContext
(or TaskScheduler
) and use that from a background thread to execute code on the UI thread.
How do you run on the STA thread when using a Task
An example by using an 'old fashioned' thread:
var task = new Task<bool>(IsConnectedToNetwork);
task.ContinueWith(res =>
{
bool onNetwork = res.Result;
var thread = new Thread(() =>
{
Form frm = onNetwork ? (Form)new OnNetworkForm() : new OffNetworkForm();
frm.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
});
task.Start();
Tasks - how to ensure the ContinueWith action is an STA thread?
EDIT: before you read any of the following, here's an excellent on-topic article: http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx ; You can skip my post and go directly there!
Most important part describing the root cause:
The default implementation of SynchronizationContext.Post just turns around and passes it off to the ThreadPool via QueueUserWorkItem. But (...) can derive their own context from SynchronizationContext and override the Post method to be more appropriate to the scheduler being represented.
In the case of Windows Forms, for example, the WindowsFormsSynchronizationContext implements Post to pass the delegate off to Control.BeginInvoke. For DispatcherSynchronizationContext in WPF, it calls to Dispatcher.BeginInvoke. And so on.
So, you need to use something other than the base SynchronizationContext class. Try using any of the other existing ones, or create your own. Example is included in the article.
And now, my original response:
After thinking a bit, I think the problem is that in your console application there is no thing like "message pump". The default SynchronizationContext is just a piece of lock. It prevents threads from intersecting on a resource, but it does not provide any queueing or thread selection. In general you are meant to subclass the SynchroContext to provide your own way of proper synchronization. Both WPF and WinForms provide their own subtypes.
When you Wait
on your task, most probably the MainThread gets blocked and all other are run on some random threads from the default threadpool.
Please try writing Thread IDs to the console along with the STA/MTA flag.
You will probably see:
STA: 1111
STA: 1111
MTA: 1234
If you see this, then most probably your first task is run synchronously on the calling thread and gets instantly finished, then you try to "continue" it's just 'appended' to 'the queue', but it is not started immediatelly (guessing, I dont know why so; the old task is finished, so ContinueWith could also just run it synchronously). Then main thread gets locked on wait, and since there's no message pump - it cannot switch to another job and sleeps. Then threadpool waits and sweps the lingering continuation task. Just guessing though. You could try to check this by
prepare synccontext
write "starting task1"
start task1 ( -> write "task1")
write "continuing task2" <--- add this one
continue: task2 ( -> write "task2")
wait
and check the order of messages in the log. Is "continuing" before "hello" from task1 or not?
You may also try seeing what happens if you don't create the Task1 by StartNew, but rather create it as prepared/suspended, then Continue, then start, then wait. If I'm right about the synchronous run, then in such setup main and continuation task will either both be run on the calling '1111' STA thread, or both on threadpool's '2222' thread.
Again, if all of these is right, the providing some message pump and proper SyncContext type will probably solve your issue. As I said, both WPF and WinForms provide their own subtypes. Although I don't remember the names now, you can try using them. If I remember correctly, the WPF starts its dispatcher automatically and you don't need any extra setup. I don't remember how's with WinForms. But, with the WPF's auto-start, if your ConsoleApp is actually some kind of a unit-test that will run many separate cases, you will need to shutdown the WPF's dispatcher before the cases.. but that's far from the topic now.
How to fix Task.Run from UI thread throwing STA error
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)
.
Task.StartNew() works differently on STA mode?
but now when I run the dll by clicking the button, I cannot navigate
the UI
Task != Thread
. A task may or may not use a thread to do it's work.
When you use:
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
You're telling the task factory to execute the given delegate on the current captured synchronization context, which is your UI synchronization context. When you do that, it will execute this on the UI message loop.
this worked very well until I had to run this Task on STA mode to open
windows in the dll
Opening a window should be done on the UI thread, hence why it's complaining. What you can do is defer the execution of the CPU bound work the a background thread, and once you need to manipulate the UI, marshal the work back:
// Start CPU bound work on a background thread
await Task.Run(() => strTime = StartSync.DoCpuWork(strPathFile,
licManager.idCmsMediator)));
// We're done awaiting, back onto the UI thread, Update window.
Why is Task.Delay breaking the STA state of the thread?
Hans has nailed it. Technically, your code is breaking because there's no SynchronizationContext
captured by the await
. But even if you write one, it won't be enough.
The one big problem with this approach is that your STA thread isn't pumping. STA threads must pump a Win32 message queue, or else they're not STA threads. SetApartmentState(ApartmentState.STA)
is just telling the runtime that this is an STA thread; it doesn't make it an STA thread. You have to pump messages for it to be an STA thread.
You can write that message pump yourself, though I don't know of anyone brave enough to have done this. Most people install a message pump from WinForms (a la Hans' answer) or WPF. It may also be possible to do this with a UWP message pump.
One nice side effect of using the provided message pumps is that they also provide a SynchronizationContext
(e.g., WinFormsSynchronizationContext
/ DispatcherSynchronizationContext
), so await
works naturally. Also, since every .NET UI framework defines a "run this delegate" Win32 message, the underlying Win32 message queue can also contain all the work you want to queue to your thread, so the explicit queue and its "runner" code is no longer necessary.
Related Topics
Prevent Property from Being Serialized in Web API
Can't Connect to Localhost on SQL Server Express 2012/2016
Is There a Difference Between I==0 and 0==I
Using C# to Dynamically Generate CSS Files
Upload File Through C# Using JSON Request and Restsharp
How to Load a C# Dll in Python
Send Email Using System.Net.Mail Through Gmail
Url Mapping with C# Httplistener
Compare Two Datatables to Determine Rows in One But Not the Other
How to Convert a Utf-8 String into Unicode
Why Does It Appear That My Random Number Generator Isn't Random in C#
Best Way to Read a Large File into a Byte Array in C#
Windows Service to Run Constantly