2016-02-15 5 views
4

У меня есть кнопка, которая имеет обработчик async, который вызывает ожидание по асинхронному методу. Вот как это выглядит:Reentrancy in async/wait?

private async void Button1_OnClick(object sender, RoutedEventArgs e) 
{ 
    await IpChangedReactor.UpdateIps(); 
} 

Вот как выглядит IpChangedReactor.UpdateIps():

public async Task UpdateIps() 
{ 
    await UpdateCurrentIp(); 
    await UpdateUserIps(); 
} 

Это асинхронной весь путь вниз.
Теперь у меня есть DispatcherTimer, который неоднократно называет await IpChangedReactor.UpdateIps в своем галочке.

Предположим, я нажал кнопку. Теперь обработчик событий ожидает на UpdateIps и возвращается к вызывающему, это означает, что WPF продолжит делать другие вещи. Тем временем, если таймер выстрелил, он снова вызовет UpdateIps, и теперь оба метода будут выполняться одновременно. Таким образом, я вижу, что он похож на использование 2 потоков. Возможны ли условия гонки? (Часть меня говорит «нет», потому что все работает в одном потоке, но это запутывает)

Я знаю, что методы асинхронизации не обязательно работают на отдельных потоках. Однако в этом случае это довольно запутанно.

Если бы я использовал синхронные методы здесь, он работал бы так, как ожидалось. Событие таймера будет запускаться только после завершения первого вызова.

Может кто-нибудь просветить меня?

+2

Термин, который вы пытаетесь найти, это «[Reentrancy] (https://en.wikipedia.org/wiki/Reentrancy_ (вычисления)) »:« * При вычислении компьютерная программа или подпрограмма называется реентерабельным, если ее можно прервать в середине ее выполнения, а затем безопасно вызвать снова («повторно введенный») до завершения его предыдущих вызовов. * « –

ответ

4

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

Однако могут ли существовать логические условия гонки? Конечно. Вы можете легко получить этот поток (или любой другой):

UpdateCurrentIp() - button 
UpdateCurrentIp() - Timer 
UpdateUserIps() - Timer 
UpdateUserIps() - button 

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

Как правило, вы можете избежать этих проблем путем синхронизации вызовов с использованием SemaphoreSlim, или AsyncLock (How to protect resources that may be used in a multi-threaded or async environment?):

using (await _asyncLock.LockAsync()) 
{ 
    await IpChangedReactor.UpdateIps(); 
} 

В этом случае, хотя, кажется, что просто избежать запуска нового обновления, когда один в настоящее время работает является достаточно хорошим:

if (_isUpdating) return; 

_isUpdating = true; 
try 
{ 
    await IpChangedReactor.UpdateIps(); 
} 
finally 
{ 
    _isUpdating = false; 
} 
+0

что-то вроде 'if (_isUpdating) return; _isUpdating = true' в начале метода и '_isUpdating = false' в конце метода? –

+0

Использование блокировки не будет действительно подходящим, если вы работаете в таймере, который занимает больше времени, чем галочка таймера. Вы бы хотели просто не запускать новые вызовы до тех пор, пока предыдущие не закончили, иначе вы просто получите постоянно растущее отставание. – Servy

+0

@Servy Это правда, и есть способы справиться с этим. Но на самом деле вы не должны использовать таймер для начала. Вы должны использовать периодическую задачу, которая выполняется, а затем ждет с 'Task.Delay'. – i3arnon

2

Я могу думать о ряде способов справиться с этой проблемой

1 Не обрабатывайте его

Как и i3arnon, может показаться, что не может быть проблемой одновременного вызова нескольких методов одновременно. Все зависит от реализации методов обновления. Точно так же, как вы пишете, это очень та же проблема, с которой вы сталкиваетесь в реальном многопоточном параллелизме. Если одновременное выполнение нескольких асинхронных операций не является проблемой для этих методов, вы можете игнорировать проблемы с повторной установкой.

2 Блок таймера и ждать запуска задач, чтобы закончить

Вы можете отключить таймер, ОСН блок Призывы обработчик событий, когда вы знаете, у вас есть запуская задачу асинхронной.Для этого вы можете использовать простое поле состояния или любой примитив блокировки/сигнализации. Это гарантирует, что у вас будет только одна операция, работающая в данный момент времени.

3 Отмена всех текущих операций асинхронное

Если вы хотите отменить любые операции асинхронных уже работает, вы можете использовать CancellationToken, чтобы остановить их, а затем начать новую операцию. Это описано в этой ссылке How to cancel a Task in await?

Это имеет смысл, если операция занимает много времени, и вы хотите не тратить время на выполнение операции, которая уже «устарела».

4 очереди запросы

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