Set Apartmentstate on a Task

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 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).

Async/await, ThreadPool and SetApartmentState

Stephen Toub has already written an StaTaskScheduler (archive); I recommend you use that.

You can then construct a TaskFactory using that TaskScheduler. There is no equivalent to Task.Run on the TaskFactory type but you can easily create one using StartNew and Unwrap.

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.

WatiN: The CurrentThread needs to have it's ApartmentState set to ApartmentState.STA to be able to automate Internet Explorer

I know Benjamin's already posted a 'working' answer, but I thought I'd add a couple of things I've experienced when I've got this error when trying to execute WatiN tests:
For NUnit, you should add something like this to your app.config for the tests:

  <configSections>
<sectionGroup name="NUnit">
<section name="TestRunner" type="System.Configuration.NameValueSectionHandler"/>
</sectionGroup>
</configSections>
<NUnit>
<TestRunner>
<!-- WatiN can only host IE in STA mode -->
<add key="ApartmentState" value="STA"/>
</TestRunner>
</NUnit>

In MbUnit, modify your TestFixture attribute like this:

[TestFixture(ApartmentState = ApartmentState.STA)] 

HTH,
Pete.

Ha - it's actually in the documentation. Doh!
http://watin.org/documentation/sta-apartmentstate/

Is it possible to await Thread in C#

Here is an extension method you could use to enable the awaiting of threads (inspired from this article: await anything).

public static TaskAwaiter GetAwaiter(this Thread thread)
{
return Task.Run(async () =>
{
while (thread.IsAlive)
{
await Task.Delay(100).ConfigureAwait(false);
}
}).GetAwaiter();
}

Usage example:

var thread = new Thread(() =>
{
Thread.Sleep(1000); // Simulate some background work
});
thread.IsBackground = true;
thread.Start();
await thread; // Wait asynchronously until the thread is completed
thread.Join(); // If you want to be extra sure that the thread has finished

Run code and return value in a Single-Threaded Apartment, where I can't set current apartment model

There is a slightly better way. You will have to create a new thread to guarantee that you are on an STA thread since you can't change a thread's apartment state after it starts. However you can get rid of the Thread.Join() call so that your method is actual asynchronous using TaskCompletionSource:

private static async Task<string> GetToken(string authority, string resource, string scope) // I don't control this signature, as it gets passed as a delegate
{
using (var tcs = new TaskCompletionSource<string>()) {
Thread t = new Thread(() => GetAuthToken(tcs));
t.SetApartmentState(ApartmentState.STA);
t.Start();
var token = await tcs.Task
return token;
}
}

private static void GetAuthToken(TaskCompletionSource<string> tcs)
{
try {
Credentials creds = AuthManagement.CreateCredentials(args); // this call must be STA
tcs.SetResult(creds.Token);
}
catch(Exception ex) {
tcs.SetException(ex);
}
}

Also if you need to wrap a return value in a task, use Task.FromResult() instead of Task.Run().



Related Topics



Leave a reply



Submit