Есть такой код (взят у Шилдта и немного упрощен).
using System;
using System.Threading;
class ThreadTest
{
object lk = new object();
public void Tick()
{
lock (lk)
{
for (int x = 0; x < 12; x++)
{
Console.WriteLine("tick");
Thread.Sleep(200);
Monitor.Pulse(lk);
Monitor.Wait(lk);
}
Monitor.PulseAll(lk);
}
}
public void Tock()
{
lock (lk)
{
for (int x = 0; x < 12; x++)
{
Console.WriteLine("tock");
Thread.Sleep(200);
Monitor.Pulse(lk);
Monitor.Wait(lk);
}
Monitor.PulseAll(lk);
}
}
}
class demo
{
static void Main()
{
ThreadTest thrTest = new ThreadTest();
Thread t1 = new Thread(thrTest.Tick); t1.Start();
Thread t2 = new Thread(thrTest.Tock); t2.Start();
}
}
Этот код в разных потоках запускает методы и синхронизируя их работу выводит на консоль Тик или Так. Но вот задумался, а как можно такой же функционал реализовать, но используя asynс/await
? И реально ли это ?
Да, можно, хотя это немного сложнее.
Вот в этой статье расписана механика того, как сделать это самостоятельно. Но лучше не изобретать велосипед, а воспользоваться готовой библиотекой AsyncEx. С подключением этой библиотеки можно написать просто:
readonly AsyncLock mutex = new AsyncLock();
public async Task UseLockAsync()
{
using (await mutex.LockAsync())
{
// тут вам принадлежит lock
}
}
(Оба автора — признанные специалисты в асинхронном программировании. Stephen Toub — один из разработчиков TPL в Microsoft, Stephen Cleary — автор хорошей книги Concurrency in C# Cookbook.)
По поводу конкретного кода с очерёдностью выполнения — такое можно сделать проще. Я использовал для синхронизации двух потоков AsyncBarrier
из того же Nito.AsyncEx
.
class Program
{
static void Main(string[] args)
{
new Program().Run().Wait();
}
async Task Run()
{
pingBarrier = new AsyncBarrier(2);
pongBarrier = new AsyncBarrier(2);
var ping = Ping();
var pong = Pong();
await ping;
await pong;
}
AsyncBarrier pingBarrier, pongBarrier;
async Task Ping()
{
for (int i = 0; i < 12; i++)
{
await pingBarrier.SignalAndWaitAsync();
pingBarrier = new AsyncBarrier(2);
Console.WriteLine($"ping #{i}, thread {Thread.CurrentThread.ManagedThreadId}");
await pongBarrier.SignalAndWaitAsync();
}
}
async Task Pong()
{
for (int i = 0; i < 12; i++)
{
await pingBarrier.SignalAndWaitAsync();
await pongBarrier.SignalAndWaitAsync();
pongBarrier = new AsyncBarrier(2);
Console.WriteLine($"pong #{i}, thread {Thread.CurrentThread.ManagedThreadId}");
}
}
}
Заметьте, что первоначальный пример Шилдта неправилен. Он использует Thread.Sleep
для того, чтобы «гарантировать» наличие ожидающего потока, что является грубым просчётом. Из документации на Monitor.Pulse
:
The Monitor
class does not maintain state indicating that the Pulse
method has been called. Thus, if you call Pulse
when no threads are waiting, the next thread that calls Wait
blocks as if Pulse
had never been called. If two threads are using Pulse
and Wait
to interact, this could result in a deadlock.
Впрочем, Шилдт знаменит неаккуратностью в своих книгах.
Уточнение: нет, пример Шилдта всё же правилен, Thread.Sleep
в нём не играет решающей роли. Тем не менее, приведённая цитата из MSDN всё ещё в силе, и использование Wait
/Pulse
для синхронизации потоков опасно.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Необходимо сделать авто обновление для программы созданной в winformsНашел решение тут, но по внедрению в проект, плохо работает, либо скачивает...
Очередной вопрос по навигации в WPFПытаюсь использовать MVVM Light Toolkit 5
Мое WinForms приложение может вызывать консольное, которое завершает процесс приложение WinForms и удаляет егоКогда я запускаю консольное приложение...