2016-12-08 5 views
2

ниже код объясняет идеюИспользование SynchronizationContext только Task RanToCompletion

private async void button1_Click(object sender, EventArgs e) 
    { 
     string result; 
     CancellationToken cancellationToken = new CancellationTokenSource(3000).Token; 

     try 
     { 
      result = await GetDataAsync(cancellationToken) 
       .ContextIfSuccess(); // Should use SynchronizationContext only if Task status is RanToCompletion 
     } 
     catch(OperationCanceledException) 
     { 
      /* Context is not required */ 
      return; 
     } 
     catch (Exception ex) 
     { 
      /* Context is not required otherwise it can slow down UI Thread a little bit */ 
      Log(ex.ToString()); 
      return; 
     } 

     /* UI Thread only */ 
     button1.Text = result; 
    } 

Вопрос «Можно ли сделать метод, как ContextIfSuccess()?»

+0

Почему детали реализации, такие как 'SynchronizationContext' имеет значение для вас? –

+0

Потому что я не хочу прерывать тему пользовательского интерфейса без причины, особенно на мобильных устройствах. – Miles

+1

Затем используйте асинхронный буфер для регистрации? log4j предоставляет это из коробки. Кроме того, это действительно заметный успех? –

ответ

2

Чтобы получить необходимый метод, вам необходимо создать пользовательский awaiter. В основном это шаблон, ключ - это просто, что, когда его просят добавить продолжение, вы добавляете его с использованием текущего контекста синхронизации для успешного завершения и используя планировщик по умолчанию, когда он не запускается до завершения.

public struct CaptureContextOnSuccessAwaiter : INotifyCompletion 
{ 
    private Task task; 

    public CaptureContextOnSuccessAwaiter(Task task) 
    { 
     this.task = task; 
    } 

    public CaptureContextOnSuccessAwaiter GetAwaiter() { return this; } 

    public void OnCompleted(Action continuation) 
    { 
     if (SynchronizationContext.Current != null) 
     { 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.OnlyOnRanToCompletion, 
       TaskScheduler.FromCurrentSynchronizationContext()); 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.NotOnRanToCompletion, 
       TaskScheduler.Default); 
     } 
     else 
     { 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.None, 
       TaskScheduler.Default); 
     } 
    } 

    public void GetResult() { task.GetAwaiter().GetResult(); } 
    public bool IsCompleted { get { return task.GetAwaiter().IsCompleted; } } 
} 

public struct CaptureContextOnSuccessAwaiter<T> : INotifyCompletion 
{ 
    private Task<T> task; 

    public CaptureContextOnSuccessAwaiter(Task<T> task) 
    { 
     this.task = task; 
    } 

    public CaptureContextOnSuccessAwaiter<T> GetAwaiter() { return this; } 

    public void OnCompleted(Action continuation) 
    { 
     if (SynchronizationContext.Current != null) 
     { 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.OnlyOnRanToCompletion, 
       TaskScheduler.FromCurrentSynchronizationContext()); 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.NotOnRanToCompletion, 
       TaskScheduler.Default); 
     } 
     else 
     { 
      task.ContinueWith(t => continuation(), 
       CancellationToken.None, 
       TaskContinuationOptions.None, 
       TaskScheduler.Default); 
     } 
    } 

    public T GetResult() { return task.GetAwaiter().GetResult(); } 
    public bool IsCompleted { get { return task.GetAwaiter().IsCompleted; } } 
} 
public static CaptureContextOnSuccessAwaiter ContextIfSuccess(this Task task) 
{ 
    return new CaptureContextOnSuccessAwaiter(task); 
} 

public static CaptureContextOnSuccessAwaiter<T> ContextIfSuccess<T>(this Task<T> task) 
{ 
    return new CaptureContextOnSuccessAwaiter<T>(task); 
} 
+0

Но делает task.ContinueWith без «ожидания» будет утилизирован GC в любое время? – Miles

+1

@Miles 'ContinueWith' будет вызываться *, когда объект ожидает *. Это обычай awaiter, поэтому компилятор будет генерировать вызов 'OnCompleted' всякий раз, когда объект ожидает. Что касается GC, то из нее, нет, будет ссылка на продолжение текущей задачи, которая будет иметь ссылки на нее из планировщика, который будет внедрен. Вам в принципе никогда не нужно беспокоиться о продолжении, которое теоретически можно было бы назвать в будущем, когда-либо собиравшемся; любая задача, которая могла бы предотвратить ее продолжение, должна быть жива. – Servy

+0

Спасибо за это объяснение. У меня есть еще один вопрос. Было бы лучше сделать это таким образом? ! [Действительный XHTML] (http://joxi.ru/5mdEazUkkDQV21.jpg) – Miles

1

await без контекста (ConfigureAwait(false)). Затем, если искомое условие истинно, вы переключаетесь в контекст.

Для этого вам нужно будет захватить SynchronizationContext.Current и Post.

Это очень похоже на то, что TaskAwaiter все равно. Он возобновился без контекста, а затем переключился обратно в контекст, если этого требует вызывающий.

Вы должны уметь это сделать в методе ContextIfSuccess. В основном, клонировать исходный код TaskAwaiter и решить, следует ли Post или нет в уведомлении о завершении. Эта функция уже существует, я предполагаю. Код должен смотреть на значение ConfigureAwait(...) и условно применять контекст или нет.

+0

Я понимаю, что вы указываете, но идея заключалась в том, чтобы сделать его простым, как единственный метод «ContextIfSuccess()» – Miles

+0

Почему бы не 'button1.Invoke' вместо этого? –

+0

Я предпочитаю button1.Text = ждать GetDataAsync(); вместо Invoke UI Thread вручную – Miles

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