I have upgraded a .NET 4.0 WinForms program to .NET 4.5.1 in the hope of using the new await on async WCF calls in order to prevent freezing the UI while waiting for data (the original was quickly written so I was hoping the old synchronous WCF calls could be made async with minimal change to existing code using the new await feature).
From what I understand, await was supposed to return to the UI thread with no extra coding, but for some reason it does not for me, so the following would give the cross thread exception:
private async void button_Click(object sender, EventArgs e)
{
using (MyService.MyWCFClient myClient = MyServiceConnectFactory.GetForUser())
{
var list=await myClient.GetListAsync();
dataGrid.DataSource=list; // fails if not on UI thread
}
}
Following the article await anything I made a custom awaiter so I could issue an await this
to get back on the UI thread, which solved the exception, but then I found my UI was still frozen despite using the asynchronously Tasks generated by Visual Studio 2013 for my WCF service.
Now the program is actually a Hydra VisualPlugin running inside an old Delphi application, so if anything could mess things up, is probably happens... But does anybody have any experience with what exactly could make awaiting an async WCF not returning to the UI thread or hang the UI thread? Maybe upgrading from 4.0 to 4.5.1 makes the program miss some reference to do the magic?
Now while I would like to understand why await does not work as advertised, I ended up with making my own workaround: A custom awaiter that forces a task to run in a background thread, and which forces the continuation to arrive back on the UI thread.
Similarly to .ConfigureAwait(false)
I wrote an .RunWithReturnToUIThread(this)
extension for Taks as follows:
public static RunWithReturnToUIThreadAwaiter<T> RunWithReturnToUIThread<T>(this Task<T> task, Control control)
{
return new RunWithReturnToUIThreadAwaiter<T>(task, control);
}
public class RunWithReturnToUIThreadAwaiter<T> : INotifyCompletion
{
private readonly Control m_control;
private readonly Task<T> m_task;
private T m_result;
private bool m_hasResult=false;
private ExceptionDispatchInfo m_ex=null; // Exception
public RunWithReturnToUIThreadAwaiter(Task<T> task, Control control)
{
if (task == null) throw new ArgumentNullException("task");
if (control == null) throw new ArgumentNullException("control");
m_task = task;
m_control = control;
}
public RunWithReturnToUIThreadAwaiter<T> GetAwaiter() { return this; }
public bool IsCompleted
{
get
{
return !m_control.InvokeRequired && m_task.IsCompleted; // never skip the OnCompleted event if invoke is required to get back on UI thread
}
}
public void OnCompleted(Action continuation)
{
// note to self: OnCompleted is not an event - it is called to specify WHAT should be continued with ONCE the result is ready, so this would be the place to launch stuff async that ends with doing "continuation":
Task.Run(async () =>
{
try
{
m_result = await m_task.ConfigureAwait(false); // await doing the actual work
m_hasResult = true;
}
catch (Exception ex)
{
m_ex = ExceptionDispatchInfo.Capture(ex); // remember exception
}
finally
{
m_control.BeginInvoke(continuation); // give control back to continue on UI thread even if ended in exception
}
});
}
public T GetResult()
{
if (m_ex == null)
{
if (m_hasResult)
return m_result;
else
return m_task.Result; // if IsCompleted returned true then OnCompleted was never run, so get the result here
}
else
{ // if ended in exception, rethrow it
m_ex.Throw();
throw m_ex.SourceException; // just to avoid compiler warning - the above does the work
}
}
}
Now in the above I am not sure if my exception handling is needed like this, or if the Task.Run really need to use async and await in its code, or if the multiple layers of Tasks could give problems (I am basically bypassing the encapsulated Task's own method of returning - since it did not return correctly in my program for WCF services).
Any comments/ideas regarding efficiency of the above workaround, or what caused the problems to begin with?