4

Если Задача, открытая моим объектом TaskCompletionSource, никогда не может быть вызвана, как я могу выполнить вычисление результата, если и до тех пор, пока кто-то не выполнит задачу?Как реализовать ленивый источник TaskCompletion?

Например, я хочу заблокировать другие асинхронные потоки выполнения до тех пор, пока не будет выдано сообщение ManualResetEvent, используя следующую функцию WaitOneAsync. Я завершаю TaskCompleationSource в обратном вызове ThreadPool.RegisterWaitForSingleObject, который происходит, когда сигнализируется WaitHandle. Но если никто не ждет задания, то я не хочу RegisterWaitForSingleObject (и я не хочу RegisterWaitForSingleObject, если задача ожидается после сообщения WaitHandle).

Как я могу изменить WaitOneAsync так, чтобы работа по вычислению результата в RegisterWaitForSingleObject выполнялась только после того, как кто-то ждет TaskCompleationSource.Task?

Я считаю, что ответ может лежать в обычае TaskAwaiter, как описано здесь Implement AsyncManualResetEvent using Lazy<T> to determine if the task has been awaited Скотт Чемберлена, но я совсем не могу получить из его примера в моем решении ... :(

public static async Task<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result) { 

    var tcs = new TaskCompletionSource<T>(); 

    RegisteredWaitHandle rwh = null; 
    rwh = ThreadPool.RegisterWaitForSingleObject(
     waitObject: waitHandle, 
     callBack: (s, t) => { 
      rwh.Unregister(null); 
      tcs.TrySetResult(result()); 
     }, 
     state: null, 
     millisecondsTimeOutInterval: -1, 
     executeOnlyOnce: true 
    ); 

    return await tcs.Task; 
} 

ответ

4

Как сказал Урр, невозможно что-то сделать в ответ на Task, являющийся await ред. Но если вы в порядке с использованием пользовательских ожидающих, то вы можете.

Самый простой способ сделать это состоит в использовании AsyncLazy from Stephen Cleary's AsyncEx:

private static Task<T> WaitOneAsyncImpl<T>(WaitHandle waitHandle, Func<T> result) 
{ 
    if (waitHandle.WaitOne(0)) 
     return Task.FromResult(result()); 

    var tcs = new TaskCompletionSource<T>(); 

    RegisteredWaitHandle rwh = null; 
    rwh = ThreadPool.RegisterWaitForSingleObject(
     waitObject: waitHandle, 
     callBack: (s, t) => 
     { 
      rwh.Unregister(null); 
      tcs.TrySetResult(result()); 
     }, 
     state: null, 
     millisecondsTimeOutInterval: -1, 
     executeOnlyOnce: true 
    ); 

    return tcs.Task; 
} 

public static AsyncLazy<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result) 
    => new AsyncLazy<T>(() => WaitOneAsyncImpl(waitHandle, result)); 
+1

@ChristopherKing Также проверьте ['AsyncFactory.FromWaitHandle'] (http://dotnetapis.com/pkg/Nito.AsyncEx/3.0.1/net45/doc/Nito.AsyncEx.AsyncFactory/FromWaitHandle (System.Threading.WaitHandle)) –

1

Это не возможно точно так же, как вы описали свои требования. TPL не предоставляет событие или обратный вызов, когда кто-то добавляет продолжение или ждет выполнения задачи.

Таким образом, вам необходимо структурировать API так, чтобы на самом деле были созданы только те задачи, которые необходимы. Что это?

public static Func<Task<T>> CreateWaitOneAsyncFactory<T>(this WaitHandle waitHandle, Func<T> result) { 
return() => WaitOneAsync(waitHandle, result); 
} 

Это возвращает фабрику задач вместо задачи. Это обман, но единственные возможные решения включают в себя обман такого рода.

Вы также можете вернуть свой заказ. Но это вообще не связано с задачами, и оно не учитывает возможности компиляции задач. В основном это концепция C#. Выявление их может привести к нечистым API.


Unrelated на ваш вопрос: Вы можете удалить await tcs.Task и вернуть эту задачу непосредственно. Кроме того, функция result не требуется. Верните a Task, который не имеет результата. Затем абоненты могут добавить результат, если они того пожелают. Это делает API WaitOneAsync чище.

+0

Usr, хорошо знать TPL не обеспечивает это из коробки, так что я не хватает чего-то очевидное. Есть ли способ расширить (например, «взломать») TPL, чтобы добавить эту функциональность? Похоже, что Скотт использует ICriticalNotifyCompletion. [Unsafe] OnCompleted для перехвата событий продолжения присоединения. Если это так, то я надеялся, что кто-то с некоторым опытом с этими недокументированными API-интерфейсами TPL сантехники подумает, что это достойный общий сценарий и сможет приготовить решение. –

+0

То, что он делает, полностью документировано и концептуально не сложно. Он выполняет обычай, ожидаемый. Это уже не задача. Вы можете только ждать этого, но не использовать его с API-интерфейсами задач. – usr

+0

Ах. Ну, тогда я бы не понял, как превратить его работу в задачу! Я пойду, что вы предлагаете, но я буду использовать «Lazy >», чтобы разгрузить работу по созданию только одного объекта TaskCompletionSource. Благодаря! –

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

  • Нет связанных вопросов^_^