0

Я использую блок приложений Application Application Transient Fault для обработки приложений в Windows 8 Store с клиентом служб данных WCF для ODATA. Я хочу использовать логику повтора для временных ошибок, возникающих при вызове службы ODATA. Я создал специальную стратегию обнаружения ошибок при переходе.EntLib TransientFaultHandling RetryPolicy.ExecuteAsync-контекст синхронизации

Я также построил метод расширения LoadTaskAsync для DataServiceCollection, поскольку метод LoadAsync не возвращает задачу (вместо этого DataServiceCollection вызывает событие LoadCompleted).

Так что я могу загрузить данные в DataServiceCollection следующим образом:

var query = this.DataContext.Products.Where(item => item.Modified >= anchor); 
var products = new DataServiceCollection<Product>(this.DataContext); 
await this.retryPolicy.ExecuteAsync(() => products.LoadTaskAsync(query)); 

Теперь документация по Enterprise Library Transient Обработка ошибок Блок приложений утверждает, что

taskFunc аргумент вы передаете ExecuteAsync метод не обязательно вызывается в том же контексте синхронизации, который использовался при вызове ExecuteAsync изначально; поэтому, если вам нужно запустить задачу из потока пользовательского интерфейса, например, не забудьте запланировать его явно внутри делегата.

Мне нужно вызвать метод LoadTaskAsync в потоке пользовательского интерфейса, поскольку операция загрузки может обновлять продукты, которые уже отслеживаются контекстом данных и которые связаны данными с пользовательским интерфейсом.

Вопрос в том, как? Предпочтительно, не изменяя метод расширения LoadTaskAsync (что, если бы не мой код для изменения). Я думал о создании метода расширения для RetryPolicy, который вызывает метод ExecuteAsync, при этом убедитесь, что taskFunc вызывается в потоке пользовательского интерфейса.

Самый простой способ, возможно, является изменение метода расширения LoadTaskAsync пройти в TaskCreationOptions.AttachedToParent, так что я мог бы создать метод расширения для RetryPolicy следующим образом:

public static Task<TResult> ExecuteCurrentSynchronizationContextAsync<TResult>(
     this RetryPolicy retryPolicy, 
     Func<TaskCreationOptions, Task<TResult>> taskFunc) 
    { 
     var scheduler = TaskScheduler.FromCurrentSynchronizationContext(); 

     return 
      retryPolicy.ExecuteAsync(
       () => 
       Task.Factory.StartNew(
        () => taskFunc(TaskCreationOptions.AttachedToParent), 
        CancellationToken.None, 
        TaskCreationOptions.None, 
        scheduler).Unwrap()); 
    } 

Обратите внимание, что taskFunc теперь должен быть Func < TaskCreationOptions, Task <TResult> >.

Я бы тогда называть это следующим образом:

 await 
      this.retryPolicy.ExecuteCurrentSynchronizationContextAsync(
       creationOptions => products.LoadTaskAsync(query, creationOptions)); 

Я предпочитаю не менять метод расширения LoadTaskAsync, как я мог бы изменить этот метод расширения RetryPolicy ExecuteCurrentSynchronizationContextAsync так, что taskFunc может быть Func < Задача <TResult> > еще раз убедитесь, что taskFunc вызывается в потоке пользовательского интерфейса?

ответ

1

Я бы не рекомендовал использовать AttachedToParent с асинхронными задачами. Фактически, большинство задач в стиле обеда будут указывать DenyChildAttach which prevents AttachedToParent from working.

Вместо этого вам просто нужно захватить сам контекст синхронизации и использовать его. Существует морщина: приложения Windows Store don't allow synchronous invocation on the synchronization context, поэтому вам нужно либо использовать CoreDispatcher.RunAsync вместо SynchronizationContext, либо создать собственный метод расширения async -aware для SynchronizationContext. Из этих двух я предпочитаю использовать подход SynchronizationContext; это немного больше кода в этом случае, но это означает, что вам не нужно связывать ваш код (предположительно код уровня обслуживания) с этим конкретным интерфейсом пользовательского интерфейса.

Итак, сначала мы определим RunAsync на SynchronizationContext, который (асинхронно) выполнить асинхронный код на указанный контекст синхронизации:

public static Task<TResult> RunAsync<TResult>(this SynchronizationContext context, Func<Task<TResult>> func) 
{ 
    var tcs = new TaskCompletionSource<TResult>(); 
    context.Post(async _ => 
    { 
     try 
     { 
      tcs.TrySetResult(await func()); 
     } 
     catch (OperationCanceledException) 
     { 
      tcs.TrySetCanceled(); 
     } 
     catch (Exception ex) 
     { 
      tcs.TrySetException(ex); 
     } 
    }, null); 
    return tcs.Task; 
} 

Тогда можно захватить и использовать SynchronizationContext:

public static Task<TResult> ExecuteOnCurrentSynchronizationContextAsync<TResult>(
    this RetryPolicy retryPolicy, 
    Func<Task<TResult>> taskFunc) 
{ 
    var context = SynchronizationContext.Current ?? new SynchronizationContext(); 

    return retryPolicy.ExecuteAsync(() => context.RunAsync(taskFunc)); 
} 
+0

Блестящее решение. Это подходит для меня. Большое спасибо. –

Смежные вопросы