51

I am trying to set the apartment state on a task but see no option in doing this. Is there a way to do this using a Task?

for (int i = 0; i < zom.Count; i++)
{
     Task t = Task.Factory.StartNew(zom[i].Process);
     t.Wait();
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Luke101
  • 63,072
  • 85
  • 231
  • 359
  • http://stackoverflow.com/questions/5971686/how-to-create-a-task-tpl-running-a-sta-thread – Mike Perrenoud May 23 '13 at 17:43
  • 1
    Yes, I have looked at the code in your link already but still cannot get my code above to work using the continuwwith. – Luke101 May 23 '13 at 17:54
  • So what happens if you implement it like it was in the answer provided at that link? – Mike Perrenoud May 23 '13 at 18:00
  • 6
    You would only be interested in that if you wanted an STA. That's the antipode of a task, an STA thread can't be a threadpool thread and *must* pump a message loop. You'll need a regular Thread, call its SetApartmentState() method and Application.Run(). – Hans Passant May 23 '13 at 18:23

5 Answers5

105

When StartNew fails you just do it yourself:

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;
}

(You can create one for Task that will look almost identical, or add overloads for some of the various options that StartNew has.)

Servy
  • 202,030
  • 26
  • 332
  • 449
  • 3
    Shouldn't you add a `System.Windows.Threading.Dispatcher.CurrentDispatcher.InvokeShutdown()` after the try-catch to clean up the dispatcher resources? – John Mar 19 '15 at 14:31
  • 1
    @John There no dispatcher to shut down. None was ever created. This method isn't even necessarily used in a WPF context. – Servy Mar 19 '15 at 14:33
  • I see. I personally need it for WPF - so in my case I should insert the `InvokeShutdown`, right? – John Mar 19 '15 at 17:29
  • 1
    @John Why would you? You have no dispatcher to shut down, as none is created here. – Servy Mar 19 '15 at 17:58
  • I was under the impression that using WPF controls requires and creates a dispatcher (for doing out-of-line layouting, for example). I might be wrong though. – John Mar 19 '15 at 18:21
  • @John Creating a dispatcher creates a dispatcher. None of the code in this answer creates a dispatcher. If the delegate you pass to this creates one, it'll need to clean it up. Or if you edit the code to create a dispatcher, then you'll need to clean it up. – Servy Mar 19 '15 at 18:22
  • "If you attempt to get the CurrentDispatcher for the current thread and a Dispatcher is not associated with the thread, a Dispatcher will be created." - from msdn. That means it's very likely you're going to have one if you do WPF-related stuff, doesn't it? – John Mar 19 '15 at 18:28
  • Then again, I stand by my statement. If you're executing code that's creating a dispatcher, then you'll need to shut it down. This code is not creating a dispatcher, and as such, isn't going to try to clean up a resource that is entirely outside of its scope. If you're using this in conjunction with a WPF and creating a dispatcher that requires cleaning up, then go clean it up. It has nothing to do with this method, rather it is a requirement for your particular delegate that you pass to it. – Servy Mar 19 '15 at 18:31
  • 3
    Hence my question having been "I personally need it for WPF - so in my case I should insert the InvokeShutdown, right?". But thanks for nothing. – John Mar 19 '15 at 18:32
  • @John It doesn't belong in this method, no. If your delegate is creating a resource that it needs to clean up, it should clean it up. It has nothing whatsoever to do with how you create a task that runs a delegate on an STA thread. If you want to create a separate method that is specifically designed for your specific circumstances, and in those circumstances you have some unique business requirements you can do whatever you want in such a method. – Servy Mar 19 '15 at 18:35
  • 2
    @John You should clean up resources in the same scope you created them. If you create a dispatcher elsewhere, clean it up there. – Liz Av Sep 30 '15 at 15:26
  • @Servy I think it would be a good idea to set `thread.IsBackground = true;` as Task's use background threads, too. – Jürgen Steinblock Dec 09 '16 at 13:50
  • @JürgenSteinblock Clearly the purpose of something like this is *not* designed to be used by a background thread. It would very likely be wrong for just about any usage in this context. While it is true that an operation like `Task.Run` is specifically designed to represent asynchronous worker processes performing work in another thread, that doesn't mean tasks necessarily represent the use of background threads. They do no. They represent asynchronous operations. – Servy Dec 09 '16 at 14:16
  • @Servy Well `Background threads are identical to foreground threads, except that background threads do not prevent a process from terminating.` (from MSDN). If the `func` will hang and if you close your main form (for a winforms app) the process will still be alive. TPL Tasks internally uses background threads, so it would be a good idea to set the `IsBackground` property to be compatible (I would expect a Task to shutdown if I unload an AppDomain. – Jürgen Steinblock Dec 09 '16 at 14:43
  • @JürgenSteinblock I'm well aware what a background task means. If the operation being performed here requires an STA thread, odds a *very* high it's not a background operation, but a foreground operation, and as such, the application should not shut down if it is the only non-background thread. Again, that other task returning operations *do* logically represent background work doesn't mean *all* task returning operations represent background work. If you unload the app domain then this operation *would* stop. Rather, the app domain won't *shut itself down* while this operation is running. – Servy Dec 09 '16 at 14:46
  • what about a overload with `CancellationToken`? – JobaDiniz May 24 '18 at 16:01
  • @JobaDiniz Just have the function close over the cancellation token if you need one. There's no reason for this function to be aware of the cancellation token that it might be using. – Servy May 24 '18 at 16:07
  • how come? I need to abort the thread `thread.Abort` when cancellation is requested... I changed the code with `cancellation.Register(thread.Abort)` and `cancellation.Register(tsc.SetCancelled)` – JobaDiniz May 24 '18 at 16:09
  • 1
    @JobaDiniz You most certainly should not be aborting threads. It's an *extremely* dangerous thing to be doing. Have whatever work your doing actually respond to the cancellation token if you want it to be able to stop its work when the cancellation token indicates as much. – Servy May 24 '18 at 16:11
  • You're right... In the `Task.Run(action, cancellationToken)` the token is only used to verify whether the thread should be started, because if the token is already cancelled, the thread shouldn't start. So that should be also the behavior here. – JobaDiniz May 24 '18 at 16:14
  • 1
    @JobaDiniz I've never found that overload to be useful (it almost exclusively just confuses people into thinking that it's a non-cooperative cancellation, which it isn't), but if you feel that it's useful in your situation to have a cancellation token that you use to determine if you should start the thread then sure, you could add that to your solution. – Servy May 24 '18 at 16:18
  • Could you provide an example of how to use this with a function that has several parameters? My code right now looks like this: var result = await Task.Run(() => MyMethod(a, b, c)); But I'd like to run this task using STA. – Fred Aug 01 '18 at 13:08
  • 2
    This code sets the apartment state to STA but does not do pumping, which is a requirement of STA threads. So there's an implicit assumption that `func` will do pumping. Use with care. – Stephen Cleary Jul 26 '21 at 14:53
23

An overload of Servy's answer to start a void Task

public static Task StartSTATask(Action func)
{
    var tcs = new TaskCompletionSource<object>();
    var thread = new Thread(() =>
    {
        try
        {
            func();
            tcs.SetResult(null);
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return tcs.Task;
}
David
  • 818
  • 13
  • 30
  • I used this one and it worked for me I was able to create (in the Action) a new WPF UserControl that has processing on its xaml.ca. Then I print it using PrintVisual without having the nasty exception of STAThread. Thank you – Muhannad Dec 27 '18 at 21:51
  • 1
    After 24h lost in several threads, this is the one that helped me. Winforms witn VS 2022 and C#10. – Marcelo Scofano Diniz May 03 '23 at 15:29
13

You could for example make a new task as follows:

       try
        {
            Task reportTask = Task.Factory.StartNew(
                () =>
                {
                    Report report = new Report(this._manager);
                    report.ExporterPDF();
                }
                , CancellationToken.None
                , TaskCreationOptions.None
                , TaskScheduler.FromCurrentSynchronizationContext()
                );

            reportTask.Wait();
        }
        catch (AggregateException ex)
        {
            foreach(var exception in ex.InnerExceptions)
            {
                throw ex.InnerException;
            }
        }
BEN'S
  • 165
  • 2
  • 6
    Is this really setting the Apartment state!? – Alexander Pacha Mar 15 '15 at 12:40
  • 2
    oh my frakking god! thank you for this snippet!!! I spent last 2 hours looking for this because even though I implemented MessageFilter according to msdn I was still getting that frakking 'busy' exception and filter methods weren't called at all. all was ok until I was running this in plain command line but then I wanted little UI to provide parameters for solution generation in more convenient way and have progress updates on UI from task executing DTE stuff. and hell broke loose... THANK YOU! – grapkulec Apr 01 '15 at 17:21
  • 5
    This will only throw the first exception from ex.InnerExceptions. – Liz Av Sep 30 '15 at 15:23
4

This is a good use case for the Task constructor, and the RunSynchronously method.

public static Task<T> RunSTATask<T>(Func<T> function)
{
    var task = new Task<T>(function, TaskCreationOptions.DenyChildAttach);
    var thread = new Thread(task.RunSynchronously);
    thread.IsBackground = true;
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    return task;
}

The purpose of TaskCreationOptions.DenyChildAttach is to make the resulting task behave identically to the Servy's solution (attaching a child task to a parent TaskCompletionSource.Task is not possible). Denying children to be attached is also the behavior of the Task.Run method.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
1

This is what I'm using with Action since I don't need to return anything:

public static class TaskUtil
{
    public static Task StartSTATask(Action action)
    {
        var tcs = new TaskCompletionSource<object>();
        var thread = new Thread(() =>
        {
            try
            {
                action();
                tcs.SetResult(new object());
            }
            catch (Exception e)
            {
                tcs.SetException(e);
            }
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        return tcs.Task;
    }
}

Where I call it like this:

TaskUtil.StartSTATask(async () => await RefreshRecords());

For details, please see https://github.com/xunit/xunit/issues/103 and Func vs. Action vs. Predicate

FYI, this is the exception I was getting where I needed to set the apartment state:

System.InvalidOperationException occurred HResult=-2146233079
Message=The calling thread must be STA, because many UI components require this. Source=PresentationCore StackTrace: at System.Windows.Input.InputManager..ctor() at System.Windows.Input.InputManager.GetCurrentInputManagerImpl() at System.Windows.Input.Keyboard.ClearFocus()

user8128167
  • 6,929
  • 6
  • 66
  • 79