Я обновил программу .NET 4.0 WinForms до .NET 4.5.1 в надежде на использование нового ожидания для вызовов async WCF, чтобы предотвратить замораживание пользовательского интерфейса при ожидании данных (оригинал был быстро написан, поэтому я надеялся, что старые синхронные вызовы WCF могут быть сделаны async с минимальным изменением существующего кода с использованием новой функции ожидания).Ожидание вызова async WCF не возвращается к потоку пользовательского интерфейса и/или блокирует поток пользовательского интерфейса
Из того, что я понимаю, Await должен был вернуться в поток пользовательского интерфейса без дополнительного кодирования, но по какой-то причине это не для меня, так что следующий будет давать перекрестную исключение резьбы:
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
}
}
После статьи await anything я сделал пользовательский awaiter, чтобы я мог опубликовать await this
, чтобы вернуться к потоку пользовательского интерфейса, который решил исключение, но затем я обнаружил, что мой пользовательский интерфейс все еще заморожен, несмотря на использование асинхронных задач, созданных Visual Studio 2013 для моего WCF оказание услуг.
Теперь программа на самом деле представляет собой Hydra VisualPlugin, запущенную в старом приложении Delphi, поэтому, если что-то может испортить вещи, возможно, произойдет ... Но есть ли у кого-нибудь какие-либо ощущения, что именно может ожидать ожидание async WCF, не возвращающегося к потоку пользовательского интерфейса или повесить поток пользовательского интерфейса? Возможно, обновление с 4.0 до 4.5.1 заставляет программу пропустить некоторую ссылку на магию?
Теперь, когда я хотел бы понять, почему ожидание не работает так, как рекламировалось, я закончил тем, что создал свой собственный метод обхода: пользовательский awaiter, который заставляет задачу запускаться в фоновом потоке и которая вынуждает продолжение возвращаться в потоке пользовательского интерфейса. Аналогично .ConfigureAwait(false)
я написал .RunWithReturnToUIThread(this)
расширение для Такс следующим образом:
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).
Любые комментарии/идеи относительно эффективности вышеупомянутого обходного пути, или с чего начались проблемы?
К сожалению, «SynchronizationContext.Current» не является нулевым ни до, ни после ожидания, но да - контекст синхронизации или планировщик задач, вероятно, нестандартен, потому что поток .NET UI разделяет обработку сообщений с помощью программы Delphi, что вызывает логику ожидания как-то не получается. Приятно знать, что это нормальная проблема с плагином, а не только отсутствие какой-либо магии в настройке WCF - спасибо! –
Если 'SynchronizationContext.Current' является' WinFormsSynchronizationContext' перед 'await', то он должен * должен * возобновляться в потоке пользовательского интерфейса ... –