I'm working with ExcelDna and async functions. If there's an exception in the async:d code I want to show a fancy WPF error window. My problem is that I'm getting the error "The calling thread must be STA, because many UI components require this." How can I solve this?
[ExcelFunction(Description = "", Category = "")]
public static async Task<object> /*string*/ Foo(CancellationToken ct)
{
try
{
return await Task.Run(async () =>
{
await Task.Delay(1000, ct);
throw new Exception("BOO");
return "HelloWorld";
}, ct2.Token);
}
catch (Exception e)
{
return ShowWpfErrorWindowThatRequiresSTA(e);
}
}
When your Excel function runs there is no SynchronizationContext.Current installed, so the async/await mechanism will runs the code after await (including your catch handler) on a ThreadPool thread. That's not a context where you can directly show your WPF form.
Installing a DispatcherSynchronizationContext corresponding to a Dispatcher running on the main thread (or another thread) would work, but you have to do that for every UDF call. Somehow the native code path through Excel loses the .NET call context on the main thread, so the SynchronizationContext gets lost.
Better is probably to assume that the catch handler is running on a ThreadPool thread, and make a SynchronizationContext.Post call from the catch handler to take you back to the main thread running your Dispatcher and WPF form.
You can look at how Excel-DNA implements the (WinForms) LogDisplay window. (https://github.com/Excel-DNA/ExcelDna/blob/master/Source/ExcelDna.Integration/LogDisplay.cs). You can call LogDisplay.WriteLine(...) from any thread, and it will do a _syncContext.Post to run the 'Show' on the main thread.
The C# async/await mechanism works less well with Excel since the native/managed transitions, and whatever Excel does internally, messes up the thread context that needs to flow between continuations. Even on the .NET side, it's not clear how thread context is managed between AppDomains (different Excel add-ins). So it's best not to rely on the .NET runtime being able to thread any kind of context through the managed/native transitions.
Many Office plugins have a problem where SynchronizationContext.Current is null, and asynchronous continuations execute on the thread pool. I'd check the value of SynchronizationContext.Current before the first await.
I have had some success in creating a WinFormsSynchronizationContext and installing that on the thread before the first await. However, installing a WPF context would be more complex.
Related
I'm running into a situation that's making me think I don't understand async / await mechanics as well as I thought.
I've got a Windows desktop app which is mostly WPF but uses a WinForms host to show a 3rd party COM object. The process of loading the COM object is pretty slow, so I've been trying to move the creation and initialization of those objects to a task to free up the UI while that work happens, but I'm finding that in some situations when the await Task.Run() returns, it's not on the UI thread. As a result, when I change the Visible property on the WinForms host after the task returns it throws because of a cross-thread call.
The calling function looks like this:
public async Task<bool> LoadPreview(string filePath)
{
bool result;
try
{
await _semaphore.WaitAsync();
result = await Task.Run(() => CreateAndInitializePreviewer(filePath));
if (result)
{
Visible = false; // <-- occasionally crashes because I'm not on the UI thread
_currentHandler.DoPreview();
Visible = true;
}
}
finally
{
_semaphore.Release();
}
return result;
}
The code inside CreateAndInitializePreviewer does not have any async / await calls. I've verified that before the call to Task.Run() I'm always on the UI thread.
Any suggestions on what I should be looking for that would cause the await Task.Run() to come back to a different thread? Any ideas are appreciated.
The process of loading the COM object is pretty slow, so I've been trying to move the creation and initialization of those objects to a task to free up the UI while that work happens
This is probably not going to work, unless your COM object is free-threaded (which is unlikely). If you want to push that work off your UI thread, you'll probably need to create a separate STA thread to hold the COM object and marshal calls to/from that thread - meaning all calls to that COM object, since it would live in the other STA thread. It's fairly straightforward in WPF to create a second UI (STA) thread; it's quite a bit harder but still possible in WinForms.
Any suggestions on what I should be looking for that would cause the await Task.Run() to come back to a different thread?
Yes. await will capture SynchronizationContext.Current if it is not null, and it should not be null in this case. It should be either an instance of DispatcherSynchronizationContext (which continues executing by sending a message to the WPF dispatcher) or WinFormsSynchronizationContext (which continues executing by sending a message to the WinForms winproc).
My initial guess is that there's something odd going on with SynchronizationContext.Current due to the WinForms-in-WPF architecture.
I was messing up with c#'s await/async when I wrote the following program:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Test();
while (true) { }
}
static async void Test()
{
var t = Task.Run(()=> Thread.Sleep(1000));
await t;
throw new Exception("This exception happens in a worker thread");
}
}
When I run this program, I can clearly see in Visual Studio's thread window that the exception happens in a Worker Thread, rather than the Main Thread, this leads me to believe that when I await a task, my method can be finished by another thread.
However, when I open a new windows forms application and add the following event handler:
async void button1_Click(object sender, EventArgs e)
{
var task = Task.Run(() => Thread.Sleep(1000));
await task;
button1.Text = "I'm in the main thread!"; //this works
}
This works, so I notice that in a console application my Test function is resumed by a worker thread, while in a Windows Forms application, it's always resumed by the main thread.
Why is this? Is it reliable?
Yes. It is reliable.
When you do await, the current SynchronizationContext is captured so that when it continues after the await, this context will be used to execute the code.
In GUI applications, the current SynchronizationContext is a context that will execute code on the UI thread.
In Console Applications, the current SynchronizationContext is null, therefore the Task continues on a thread-pool thread.
Take a look at this blog post.
As I explain in my async intro, by default await will capture a "context" and resume execution of its async method in that context.
Technically, this context is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current.
In everyday terms, this means that the context is a UI context if the method is running on a UI thread; it is an ASP.NET request context if the method is servicing an ASP.NET request; and it is most likely the thread pool context in all other situations.
Note that this is default behavior. You can specify that the method does not need to resume on its context by awaiting the result of ConfigureAwait(continueOnCapturedContext: false). In this case, it is likely that the rest of the method will be executed on a thread pool thread; but technically this just means that the method will be executed "somewhere" and you don't care where.
Yes, this is correct and one of the intended uses of async / await.
The Task that you are awaiting is what happens asynchronously. When it resumes, it resumes on the calling thread, which will be the UI thread in the case of a WinForms event handler.
Note that it is possible for you to change this behavior using Task.ConfigureAwait(False). When the task is configured this way, it will not pass control back to the original thread and will instead resume on the task's thread.
All of this behavior is dependant on the application's current SynchonizationContext though. Some application types will have different contexts which can subtly change the behaviors. Console Applications are a good example of this, as they do not set a synchronization context by default, and will resume on the Task's thread. You can change this if you want by creating a context for the console app, as shown here.
I have developed an Outlook VSTO addin. Some tasks should be made on a background thread. Typically, checking something in my local db or invoking a web request. After reading several posts, I dropped the idea of calling the Outlook Object Model (OOM) in a background thread.
I have some wpf controls and I successfully managed to use the .NET 40 TPL to perform the async task and when completed to "finish" the job (i.e. accessing the UI or the OOM) in the Main VSTA Thread.
To do so I use a syntax of the form:
Task<SomeResult> task = Task.Factory.StartNew(()=>{
//Do long tasks that have nothing to do with UI or OOM
return SomeResult();
});
//now I need to access the OOM
task.ContinueWith((Task<SomeResult> tsk) =>{
//Do something clever using SomeResult that uses the OOM
},TaskScheduler.FromCurrentSynchronizationContext());
So far so good. But now I want to do something similar when hooking an event in the OOM where there are no Form/WPF control. Precisely, my problem comes from the fact that TaskScheduler.FromCurrentSynchronizationContext() throws an exception.
For instance,
Items inboxItems = ...;
inboxItems.ItemAdd += AddNewInboxItems;
private void AddNewInboxItems(object item)
{
Task<SomeResult> task = Task.Factory.StartNew(()=>{
//Do long tasks that have nothing to do with OOM
return SomeResult()});
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
/* Ouch TaskScheduler.FromCurrentSynchronizationContext() throws an InvalidOperationException, 'The current SynchronizationContext may not be used as a TaskScheduler.' */
task.ContinueWith((Task<SomeResult> tsk) =>{
//Do something clever using SomeResult that uses the OOM
}),scheduler};
}
/* Ouch TaskScheduler.FromCurrentSynchronizationContext() throws an InvalidOperationException, 'The current SynchronizationContext may not be used as a TaskScheduler.' */
Note that I tried to create a TaskScheduler in addin initialization and putting it in a singleton as suggested here. But it does not work, the continuation task is not performed in the desired VSTA Main thread but another one (inspected with VisualStudio).
Any idea ?
There is known bug that SynchronizationContext.Current might be null in several places where it should not (including office add-ins). That bug was fixed in .NET 4.5. But since you cannot upgrade to .NET 4.5, you have to find a workaround. As a suggestion, try to do:
System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
when initializing your addon.
You can use the SynchronizationContext class which provides the basic functionality for propagating a synchronization context in various synchronization models. The Post method dispatches an asynchronous message to a synchronization context, i.e. the Post method starts an asynchronous request to post a message. See Using SynchronizationContext for sending events back to the UI for WinForms or WPF for more information and sample code.
FYI The Current property allows to get the synchronization context for the current thread. This property is useful for propagating a synchronization context from one thread to another.
I have a WPF program where my model need to load a "Out-of-Proc" (.exe) COM component in order to achieve some validations when a user make an action on the UI. I'd like to inform the user that a long action will take place to let him know that the application is busy, not just frozen. But any action on the UI does occur after the COM action is completed.
I think that any COM communication should be done on the main UI thread. It eliminates any solution that would run on another thread than the main (UI) thread.
I tried many options without success:
MSDN Dispatcher.PushFrame (DoEvents)
StackOverflow HCL (PushFrame)
Jason programming Blog (PushFrame)
I can't see how I can achieve a synchronous action from a model where I would need to refresh the UI.
My action has a property "IsLoading" that I subscribe from my view and where I try to update the UI according to its state but it seems that is not possible in WPF ???
Any other suggestions ?
Can I use async/await and do my COM action from another thread running another dispatcher (kind of complex) and will loose required synchronicity (user need results of COM operation to continue its work) ?
Mainly for Blindy...
Some clearer explications (more details about required synchronicity):
When a user click on a TreeView item, I load a grid then need to verify that data entered in the grid is still valid. To do validation, I need to load an application through COM and automate it to load a document, then parse it and verify the data in the grid (in the Model of the Grid in the view). That takes 10 seconds.
If I do that on another thread, then user can do an action to select to add a new row in the grid which still depends on the same COM application loaded with the previous document. I still need to wait for the application to load. It is a synchronous action. My application depends on that COM application with its loaded document to be in valid state for user to take more actions. But I need to give user some feedback on what I’m doing (start COM app and load on document). Doing COM action on another thread just report the problem later but do not solve the fact that user need to wait that the action would complete. I think I need to (force) update my WPF app but can’t find any (twisted) way to do it.
[UPDATE] As the OP has updated the question and specified he's using out-of-proc COM objects, the custom STA thread plumbing described below doesn't make sense. Now, a simple await Task.Run(() => { /* call the out-of-proc COM */}) is enough to keep the UI responsive. Kudos to #acelent for clarifying this point.
Recently I answered a related question: StaTaskScheduler and STA thread message pumping.
The solution was to create and use STA COM objects on a dedicated background STA thread which provides both message pumping and thread affinity for those COM objects.
I'd like to show how ThreadWithAffinityContext can be used in your case, with async/await:
dynamic _comObject = null;
ThreadWithAffinityContext _staThread = null;
// Start the long-running task
Task NewCommandHandlerAsync()
{
// create the ThreadWithAffinityContext if haven't done this yet
if (_staThread == null)
_staThread = new ThreadWithAffinityContext(
staThread: true,
pumpMessages: true);
// create the COM Object if haven't done this yet
if (_comObject == null)
{
await _staThread.Run(() =>
{
// _comObject will live on a dedicated STA thread,
// run by ThreadWithAffinityContext
_comObject = new ComObject();
}, CancellationToken.None);
}
// use the COM object
await _staThread.Run(() =>
{
// run a lengthy process
_comObject.DoWork();
}, CancellationToken.None);
}
// keep track of pending NewCommandHandlerAsync
Task _newCommandHandler = null;
// handle a WPF command
private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
try
{
// avoid re-entrancy (i.e., running two NewCommandHandlerAsync in parallel)
if (_newCommandHandler != null)
throw new InvalidOperationException("One NewCommandHandlerAsync at a time!");
try
{
await _newCommandHandler = NewCommandHandlerAsync();
}
finally
{
_newCommandHandler = null;
}
}
catch (Exception ex)
{
// handle all exceptions possibly thrown inside "async void" method
MessageBox.Show(ex.Message);
}
}
The fact that we have offloaded the lengthy process _comObject.DoWork() to a separate thread doesn't automatically solve the other common UI-related problem:
How to handle the UI when the lengthy background operation is pending?
There is a number of options. E.g., you can disable the UI elements which fire NewCommand_Executed event, to avoid re-entrancy, and enable another UI element to allow the user to cancel the pending work (a Stop button). You should also provide some progress feedback, if your COM object supports that.
Alternatively, you can display a modal dialog before staring the long-running task and hide it when the task has completed. As far as the UI usability goes, modality is less desirable, but it's very easy to implement (example).
You can create and use COM objects on any thread, the marshaller will take care of running it on a background thread if your application uses the STA threading model. There's no need to funnel every call through the main UI thread.
As to your last question in comments, since you'll be running this on a background thread, you of course need to synchronize like usual with lock and Invoke the result back on the main thread when done. This is nothing specially related to COM, it's just how Windows threading works.
In short, stop using the UI thread for heavy duty, non-UI related work.
I used this once for WPF so as to force the screen to re-Paint :
I used auto-translation from VB so I hope it's correct
private Action EmptyDelegate = () => { };
[System.Runtime.CompilerServices.Extension()]
public void Refresh(UIElement uiElement)
{
uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
}
I'm having an issue with a WinRT project. Currently the execution on the program is running on two threads. One thread executes the main application and the other handles the UI side of things. At the moment, I'm having an issue calling a function from the main thread to execute on the UI thread, waiting for a reply and then continuing execution on the main thread... let me show you some code as an example.
public async void SignOut(Action onSuccess, Action onFailure)
{
bool success = false;
bool wait = true;
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await SignOutAsync();
success = true;
wait = false;
});
while (wait) { }
if (success)
{
onSuccess();
}
else
{
onFailure();
}
}
So this code is doing what I want it to do but it's obviously not the right way to go about it with the busy waiting and all of that. The problem is that if I move the OnSuccess/OnFailure execution into the RunAsync lambda then there is an error on the callback about invalid memory because the execution is on a different thread. Currently the problem I'm facing is that I can't remove the busy wait without screwing up the order of execution. Ideally I want to wait for the entire RunAsync lambda to finish execution on the UI thread and then return to the main thread to run the success/fail callbacks.
It appears at the moment that as soon as I hit the await SignOutAsync() part of the RunAsync lambda the RunAsync task marks itself as complete and returns to the Success/Failure check before the SignOutAsync method has any result. I believe this is due to the nested async methods and that you can't really await on the RunAsync call and then again on the async lambda within it.
Any advice would be greatly appreciated.
Currently the execution on the program is running on two threads. One thread executes the main application and the other handles the UI side of things.
This is not ideal. If at all possible, structure your code so that you only have one "special" thread (the UI thread). async allows your UI thread to remain responsive without requiring a second "special" thread.
At the moment, I'm having an issue calling a function from the main thread to execute on the UI thread, waiting for a reply and then continuing execution on the main thread.
Again, a better design is to have your program logic provide "services" to the UI, instead of the other way around. So, do your best to redesign the calls so that the UI is driving the program logic and not the opposite.
That said, if you absolutely must have a single "special" background thread, you can use the AsyncContextThread type from my AsyncEx library. AsyncContextThread understands asynchronous methods, so you can do this:
public async Task SignOutAsync(Action onSuccess, Action onFailure)
{
try
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => SignOutAsync());
onSuccess();
}
catch
{
onFailure();
}
}
However, I would be embarrassed to put this code into production; anything that uses Dispatcher is a code smell. Even though I wrote the AsyncContextThread type, I can't recommend it for Windows Store projects. A far better design is to structure the code so that the program logic never calls back into the UI.