2015-09-20 4 views
3

Я видел этот пример в конце книги Стивена.Lazy общий ресурс async - Уточнение?

Этот код может быть просмотрен более, чем один поток.

static int _simpleValue; 
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>( 
async() => 
{ 
    await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); 
    return _simpleValue++; 
}); 

async Task GetSharedIntegerAsync() 
{ 
    int sharedValue = await MySharedAsyncInteger.Value; 
} 

Независимо от того, сколько частей стоимости коды вызова одновременно, Task<int> только создаются раз и вернулись ко всем абонентам.

Но потом он говорит:

Если существуют различные типы нитей, которые могут называть Value (например, UI нить и нить нить бассейн или две разные запроса ASP.NET потоков), то может быть лучше всегда выполнять асинхронный делегат в потоке пула потоков.

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

static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(() => Task.Run(
async() => 
{ 
    await Task.Delay(TimeSpan.FromSeconds(2)); 
    return _simpleValue++;; 
})); 

Вопрос:

Я не понимаю, в чем проблема с первым код. Продолжение будет выполнено в потоке threadpool (из-за ConfigureAwait нам не нужен исходный контекст).

Также, как только любой элемент управления из любой нити достигнет await, элемент управления вернется к вызывающему абоненту.

Я не вижу, что дополнительно риск второго кода пытается решить.

Я имею в виду - что это проблема с «различных типов нитей, которые можно назвать Value» в первого кода?

+1

* Продолжение будет выполнено в потоке threadpool * Я не могу поверить, что это гарантировано договором этого метода. В лучшем случае это деталь реализации. – rene

ответ

2

В чем проблема с «разными типами потоков, которые могут вызвать значение» в первом коде?

Ничего не найдено Неверный с этим кодом. Но представьте, что у вас была работа с процессором вместе с вызовом инициализации async. Сделайте это так, например:

static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(
async() => 
{ 
    int i = 0; 
    while (i < 5) 
    { 
     Thread.Sleep(500); 
     i++; 
    } 

    await Task.Delay(TimeSpan.FromSeconds(2)); 
    return 0; 
}); 

Теперь вы не «охраняете» от подобных операций. Я предполагаю, что Стефан упомянул поток пользовательского интерфейса, потому что вы не должны выполнять какую-либо операцию, длина которой превышает 50 мс. Вы не хотите, чтобы ваш поток пользовательского интерфейса зависал.

Когда вы используете Task.Run для вызова делегата, вы закрываете себя из мест, где можно передать длинный делегат на ваш Lazy<T>.

Стефан Toub говорит об этом в AsyncLazy:

Здесь мы имеем новый AsyncLazy<T>, производный от Lazy<Task<T>> и предоставляет два конструктора. Каждый из конструкторов принимает функцию от вызывающего абонента, как и Lazy<T>. Первый конструктор, в факте, принимает тот же Func, что и Lazy<T>. Вместо того, чтобы передавать Func<T> прямо к базовому конструктору, вместо этого мы вместо передаем новый Func<Task<T>>, который просто использует StartNew для запуска , предоставленного пользователем Func<T>. Второй конструктор немного интереснее. Вместо того, чтобы принимать Func<T>, он принимает Func<Task<T>>. С помощью этой функции у нас есть два хороших варианта для решения этой проблемы. Первый просто передать функцию прямо вниз к основанию конструктора, например:

public AsyncLazy(Func<Task<T>> taskFactory) : base(taskFactory) { } 

Этот вариант работает, но это означает, что, когда пользователь получает доступ к свойству данного экземпляра Value , делегат taskFactory будет вызываться синхронно. Это может быть совершенно разумно, если делегат taskFactory очень мало работает, прежде чем возвращать экземпляр задачи. Если, однако, делегат taskFactory выполняет любую небрежную работу, то вызов значения блокируется до завершения вызова taskFactory. Для обложки этого случая второго подход, чтобы запустить taskFactory используя Task.Factory.StartNew, т.е. запустить сам делегат асинхронно, так же, как с первым конструктором, даже если этот делегат уже возвращает Task<T>.

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