В предыдущих главах мы видели, как работает параллельное программирование, создавая небольшие задачи, называемые единицами работы, которые могут выполняться одновременно одним или несколькими потоками.
Эта глава начнется с введения в различия между синхронным и асинхронным кодом, затем будет обсуждаться, когда асинхронный код уместен, а когда его следует избегать. Наконец, мы обсудим новые возможности параллельного программирования, которые помогут справиться со сложностями асинхронного кода.
Это руководство соответствует учебному проекту: Magician Dix / HandsOnParallelProgramming · GitCode.
Эта глава эквивалентна поэтапному изложению, в ней не так уж много новых знаний и, как правило, она относительно проста.
1. Тип исполнения программы
В книге рассказывается о порядке выполнения синхронных операций и асинхронных операций.Думаю, она для новичков и не требует дополнительных пояснений. Синхронное выполнение означает, что код может выполняться только построчно, сверху вниз; если он асинхронный, одновременно будут выполняться несколько блоков кода. Я считаю, что каждый может понять эти базовые знания, не вдаваясь в подробности.Понять синхронизацию и асинхронность несложно.
Пропустите здесь и перейдите к следующей части.
2. Ситуации, подходящие для использования асинхронного программирования
Когда уместно использовать асинхронное программирование?У Microsoft есть документ:
Предлагаются следующие сценарии:
-
Если операция вызывается из приложения среднего уровня .
-
Если вы вызываете операцию на странице ASP.NET, вы можете использовать асинхронную страницу.
-
Если операция вызывается из любого однопоточного приложения, такого как Windows Forms или Windows Presentation Foundation (WPF). При использовании модели асинхронного вызова на основе событий результирующее событие вызывается в потоке пользовательского интерфейса, что повышает скорость реагирования приложения, не требуя от вас самостоятельно обрабатывать несколько потоков.
После беглого взгляда это не имеет никакого отношения к нашей разработке Unity. А для разработки на Unity всегда были четкие сценарии, когда использовать асинхронную многопоточность: сеть, ввод-вывод, обработка больших объемов данных и т. д. Короче говоря, ради производительности, естественно, когда задача помещается в основной поток и вызывает лаги, необходимо подумать, стоит ли использовать асинхронную многопоточность.
3. Напишите асинхронный код
Существуют следующие способы асинхронной реализации:
-
Используйте метод Delegate.BeginInvoke;
-
Используйте класс Task;
-
Используйте интерфейс IAsyncResult;
-
Используйте ключевые слова async и await.
3.1. Используйте метод Delegate.BeginInvoke.
Это метод делегирования. Давайте посмотрим непосредственно на фрагмент кода:
private void RunWithBegionInvoke()
{
Action logAction = AsyncLogAction;
logAction.BeginInvoke(null, null);
Debug.Log("RunWithBegionInvoke !!!");
}
public static async void AsyncLogAction()
{
await Task.Delay(2500);
Debug.Log("AsyncLogAction Finish !!!");
}
Мы выполняем его напрямую, и результаты следующие:
Видно, что BeginInvoke не блокирует основной поток, а обеспечивает асинхронный эффект. В то же время эта функция также поддерживает обратные вызовы и параметры, которые, можно сказать, очень просты в использовании. Этот нижний уровень фактически реализуется с использованием IAsyncResult. Конечно, автоматическая реализация будет иметь определенные дополнительные накладные расходы.
3.2. Использование класса задач
Раньше это слишком часто использовалось, поэтому я не буду вдаваться в подробности.Вы можете обратиться к главе 2. Пропускать.
3.3. Использование интерфейса IAsyncResult
Интерфейс IAsyncResult (система) | Microsoft Learn Представляет состояние асинхронной операции. https://learn.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=netstandard-2.1 Знание синтаксиса интерфейса такого рода легче понять, если вы непосредственно его кодируете. Здесь мы можем случайно определить наш собственный IAsyncResult, и, конечно, я могу случайно записать внутрь параметры:
/// <summary>
/// IAsyncResult 的示例代码;
/// </summary>
public class MyAsyncResult : IAsyncResult
{
public MyAsyncResult(MyAsyncState state)
{
m_AutoResetEvent = new AutoResetEvent(false);
IsCompleted = false;
m_AsyncState = state;
CompletedSynchronously = false;
}
private MyAsyncState m_AsyncState;
public object AsyncState => m_AsyncState;
private AutoResetEvent m_AutoResetEvent;
public WaitHandle AsyncWaitHandle => m_AutoResetEvent;
public bool CompletedSynchronously { get; private set; }
public bool IsCompleted { get; private set; }
}
/// <summary>
/// 自定义的数据结构
/// </summary>
public class MyAsyncState
{
public Vector3 Positon;
public Quaternion Rotation;
}
Затем мы пытаемся позвонить ему:
private void RunWtihMyAsyncResult()
{
MyAsyncState state = new MyAsyncState();
state.Positon = new Vector3(50, 811, 55);
state.Rotation = Quaternion.Euler(7, 8, 9);
MyAsyncResult result = new MyAsyncResult(state);
Action logAction = AsyncLogAction;
// OnActionCallBack 的回调仍然在子线程
logAction.BeginInvoke(OnActionCallBack, result);
Debug.Log($"RunWtihMyAsyncResult : {Task.CurrentId}");
}
public static void OnActionCallBack(IAsyncResult result)
{
Debug.Log($"OnActionCallBack : {result.GetType()} | {Task.CurrentId} !");
MyAsyncResult myResult = result.AsyncState as MyAsyncResult;
MyAsyncState myState = myResult.AsyncState as MyAsyncState;
Debug.Log($"Positon : {myState.Positon}");
Debug.Log($"EulerAngles : {myState.Rotation.eulerAngles}");
Debug.Log($"IsCompleted : {result.IsCompleted}");
Debug.Log($"CompletedSynchronously : {result.CompletedSynchronously}");
Debug.Log($"OnActionCallBack : End !");
}
Как видите, обратный вызов MyAsyncResult здесь представляет собой AsyncState в возвращаемом IAsyncResult, который похож на матрешку. Реализация интерфейса, возвращаемая системой, — это класс System.Runtime.Remoting.Messaging.AsyncResult, который должен быть автоматически инкапсулирован.
Примечание. Интерфейс IAsyncResult, вызываемый BeginInvoke, все еще находится в дочернем потоке!
Конечно, описанный выше метод записи обнаружил, что написанный нами самостоятельно IAsyncResult практически бесполезен... Однако C# предоставляет общий и простой метод записи:
private void RunWtihAsyncCallBack()
{
MyAsyncState state = new MyAsyncState();
state.Positon = new Vector3(888, 831, 255);
state.Rotation = Quaternion.Euler(76, 38,329);
Action logAction = TestFunction.AsyncLogAction;
logAction.BeginInvoke(TestFunction.OnActionCallBackSimple, state);
}
Здесь также возвращается System.Runtime.Remoting.Messaging.AsyncResult. Отличие от того, что я ожидал, заключается в том, что обратный вызов выполняется до делегата, поэтому я не знаю, какое использование этого интерфейса на данный момент.
4. Ситуации, когда нецелесообразно использовать асинхронное программирование
Здесь автор предлагает 4 сценария:
-
В одной базе данных без пула соединений
-
Уделяйте больше внимания простоте чтения и сопровождения кода.
-
Простое управление и короткое время работы
-
Приложение использует много общих ресурсов
На самом деле для нашей Untiy разработки это не проблемы. Изначально Unity рекомендует делать все в основном потоке, а многопоточность — это расширенное использование. Программисты, пишущие многопоточность, обычно знают, когда ее использовать. Поскольку Unity отключает вызов многих интерфейсов в подпотоках, фактически большая часть кода, связанного с Unity, может использоваться только в основном потоке.Большинство используемых подпотоков представляют собой ввод-вывод или чистую обработку данных.
5. Разделы настоящей главы
На самом деле эта глава представляет собой резюме: в ней представлены понятия асинхронности и синхронизации, а также обсуждаются различные реализации асинхронности. Но лично мне кажется, что практической информации мало, а полезные вещи не так хороши, как упомянутые в предыдущих главах, что немного водянисто.
Это руководство соответствует учебному проекту: Magician Dix / HandsOnParallelProgramming · GitCode.