Я обновил программу .NET 4.0 WinForms до .NET 4.5.1 в надежде использовать новый await для асинхронных вызовов WCF, чтобы предотвратить зависание пользовательского интерфейса при ожидании данных (оригинал был быстро написан, поэтому я надеялся, что старый синхронные вызовы WCF можно сделать асинхронными с минимальными изменениями в существующем коде с помощью новой функции ожидания).
Насколько я понимаю, ожидание должно было вернуться в поток пользовательского интерфейса без дополнительного кодирования, но по какой-то причине это не для меня, поэтому следующее приведет к исключению перекрестного потока:
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
}
}
Следуя статье ждите чего-нибудь, я сделал пользовательский awaiter, чтобы я мог выдать await this
, чтобы вернуться к потоку пользовательского интерфейса, который разрешил исключение, но затем я обнаружил, что мой пользовательский интерфейс все еще завис, несмотря на использование асинхронно задач, созданных Visual Studio 2013 для моей службы WCF.
Теперь программа на самом деле представляет собой Hydra VisualPlugin, работающий внутри старого приложения Delphi, поэтому, если что-то может испортить ситуацию, вероятно, произойдет... Но есть ли у кого-нибудь опыт в том, что именно может сделать ожидание асинхронного WCF, не возвращающегося в поток пользовательского интерфейса или повесить UI-нить? Может быть, обновление с 4.0 до 4.5.1 приводит к тому, что программа пропускает какую-то ссылку, чтобы творить чудеса?
Теперь, когда я хотел бы понять, почему ожидание не работает так, как рекламируется, в итоге я придумал свой собственный обходной путь: настраиваемый ожидатель, который заставляет задачу выполняться в фоновом потоке и заставляет продолжение вернуться в поток пользовательского интерфейса. . Аналогично .ConfigureAwait(false)
я написал расширение .RunWithReturnToUIThread(this)
для Taks следующим образом:
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
}
}
}
Теперь в приведенном выше я не уверен, нужна ли моя обработка исключений, как это, или действительно ли Task.Run нужно использовать асинхронность и ожидание в своем коде, или если несколько уровней задач могут создавать проблемы (я в основном обхожу инкапсулировал собственный метод возврата задачи, поскольку он не возвращался правильно в моей программе для служб WCF).
Любые комментарии/идеи относительно эффективности вышеуказанного обходного пути или того, с чего начались проблемы?