83

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.

Michel Triana
  • 2,486
  • 1
  • 22
  • 31
  • 2
    For any future visitors who come looking for the real intent of the question: - Using [`StaTaskScheduler`](https://github.com/dotnet/samples/blob/9ae31f531a5f82928134f2ba6f67144e92603e01/csharp/parallel/ParallelExtensionsExtras/TaskSchedulers/StaTaskScheduler.cs) ([guide](https://devblogs.microsoft.com/pfxteam/parallelextensionsextras-tour-5-stataskscheduler/)) as pointed in this [answer](https://stackoverflow.com/a/10336082/244353) - DIY version: https://stackoverflow.com/questions/16720496/set-apartmentstate-on-a-task – Mrchief Apr 21 '14 at 20:00

2 Answers2

79

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);
dtb
  • 213,145
  • 36
  • 401
  • 431
  • 13
    Just to be clear; only the lambda within the ContinueWith method will run with the proper context, not what runs in the main task lambda. – dudeNumber4 Mar 02 '12 at 16:38
  • Thanks for this, I was trying `TaskScheduler.Current` (from within a WinForms button event handler), couldn't understand why it wasn't working... – Benjol Mar 22 '12 at 08:05
  • 12
    As a point of clarification, there is also an overload for the instance method Task.Start() which takes a TaskScheduler. The details of the question make it clear we're more interested in the continuation case, but the more general question of how to run a Task on an STA thread is not limited to only that case. I wrongly assumed at first I would need continue from an empty "fudge" task in order to run my desired STA task. – Josh Sutterfield Feb 10 '14 at 20:56
  • 2
    So shouldn't this question's title be edited to better reflect its intention? – Mrchief Apr 21 '14 at 19:56
  • 1
    Or broken down into two. "How do i start a task on an STA thread" and "How do i continue a task on an STA thread". – David Feb 11 '16 at 22:01
  • public static Task ShowOpenFolderPicker() { var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); var tokensource = new CancellationTokenSource(); return Task.Factory.StartNew(Show,tokensource.Token, TaskCreationOptions.None,scheduler); } – Juan Pablo Garcia Coello Jul 14 '16 at 08:07
  • where for instance: private static String Show() { var dialog = new System.Windows.Forms.FolderBrowserDialog(); dialog.ShowNewFolderButton = true; var result = dialog.ShowDialog(); if (result == System.Windows.Forms.DialogResult.OK) { return dialog.SelectedPath; } return null; } – Juan Pablo Garcia Coello Jul 14 '16 at 08:08
0

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)));
    }
rpaulin56
  • 436
  • 8
  • 14
  • 1
    The `ContinueWith` and the `Dispatcher` are considered old-school approaches after the advent of async/await. I would avoid using them for new development (in general). – Theodor Zoulias Jan 02 '21 at 18:06