«Как заблокировать поток кода, пока событие не будет запущено?»
Ваш подход неверен. Управляемый событиями не означает блокирование и ожидание события. Вы никогда не ждете, по крайней мере, вы всегда стараетесь избежать этого. Ожидание - это бесполезная трата ресурсов, блокировка потоков и, возможно, введение риска тупика или зомбирования потока ie (в случае, если сигнал освобождения никогда не поднимается).
Должно быть ясно, что блокировка потока на wait для события - это анти-шаблон, поскольку он противоречит идее события.
У вас обычно есть два (современных) варианта: реализовать асинхронный API или API, управляемый событиями. Поскольку вы не хотите реализовывать свой API-интерфейс асинхронным, у вас остается API, управляемый событиями.
Ключ API, управляемого событиями, заключается в том, что вместо того, чтобы заставлять вызывающего абонента синхронно ожидать результат или опросить результат, вы позволяете вызывающему продолжить и отправлять ему уведомление, как только результат будет готов. или операция завершена. Между тем, вызывающая сторона может продолжать выполнять другие операции.
Если рассматривать проблему с точки зрения многопоточности, то управляемый событиями API позволяет вызывающему потоку, например, потоку пользовательского интерфейса, который выполняет обработчик события кнопки, быть свободным, чтобы продолжать обрабатывать, например, другие связанные с пользовательским интерфейсом операции, такие как рендеринг элементов пользовательского интерфейса или обработка пользовательского ввода, такие как движение мыши и нажатия клавиш. API, управляемый событиями, имеет тот же эффект или цель, что и асинхронный API, хотя это гораздо менее удобно.
Поскольку вы не предоставили достаточно информации о том, что вы на самом деле пытаетесь сделать, что на самом деле делает Utility.PickPoint()
и каков результат задания или почему пользователь должен нажать на «Сетка», Я не могу предложить вам лучшее решение. Я просто могу предложить общую схему того, как реализовать ваше требование.
Ваш поток или цель, очевидно, разделены по крайней мере на два шага, чтобы сделать его последовательностью операций:
- Выполнить операцию 1, когда пользователь нажимает кнопку
- Выполнить операцию 2 (продолжить / завершить операцию 1), когда пользователь нажимает на
Grid
как минимум с двумя ограничениями :
- Необязательно: последовательность должна быть завершена, прежде чем клиенту API разрешат ее повторить. Последовательность завершается после завершения операции 2.
- Операция 1 всегда выполняется перед операцией 2. Операция 1 запускает последовательность.
- Операция 1 должна завершиться, прежде чем клиенту API будет разрешено выполнить операцию 2
Это требует двух уведомлений для клиента API, чтобы разрешить неблокирующее взаимодействие:
- Операция 1 завершена (или требуется взаимодействие)
- Операция 2 (или цель) завершена
Вы должны позволить своему API реализовать это поведение и ограничения, предоставив два метода publi c и два события publi c.
Реализация / рефакторинг API-интерфейса служебной программы
Utility.cs
class Utility
{
public event EventHandler InitializePickPointCompleted;
public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
private bool IsPickPointInitialized { get; set; }
private bool IsExecutingSequence { get; set; }
// The prefix 'Begin' signals the caller or client of the API,
// that he also has to end the sequence explicitly
public void BeginPickPoint(param)
{
// Implement constraint 1
if (this.IsExecutingSequence)
{
// Alternatively just return or use Try-do pattern
throw new InvalidOperationException("BeginPickPoint is already executing. Call EndPickPoint before starting another sequence.");
}
// Set the flag that a current sequence is in progress
this.IsExecutingSequence = true;
// Execute operation until caller interaction is required.
// Execute in background thread to allow API caller to proceed with execution.
Task.Run(() => StartOperationNonBlocking(param));
}
public void EndPickPoint(param)
{
// Implement constraint 2 and 3
if (!this.IsPickPointInitialized)
{
// Alternatively just return or use Try-do pattern
throw new InvalidOperationException("BeginPickPoint must have completed execution before calling EndPickPoint.");
}
// Execute operation until caller interaction is required.
// Execute in background thread to allow API caller to proceed with execution.
Task.Run(() => CompleteOperationNonBlocking(param));
}
private void StartOperationNonBlocking(param)
{
... // Do something
// Flag the completion of the first step of the sequence (to guarantee constraint 2)
this.IsPickPointInitialized = true;
// Request caller interaction to kick off EndPickPoint() execution
OnInitializePickPointCompleted();
}
private void CompleteOperationNonBlocking(param)
{
// Execute goal and get the result of the completed task
Point result = ExecuteGoal();
// Reset API sequence
this.IsExecutingSequence = false;
this.IsPickPointInitialized = false;
// Notify caller that execution has completed and the result is available
OnPickPointCompleted(result);
}
private void OnInitializePickPointCompleted()
{
// Set the result of the task
this.InitializePickPointCompleted?.Invoke(this, EventArgs.Empty);
}
private void OnPickPointCompleted(Point result)
{
// Set the result of the task
this.PickPointCompleted?.Invoke(this, new PickPointCompletedEventArgs(result));
}
}
PickPointCompletedEventArgs.cs
class PickPointCompletedEventArgs : EventArgs
{
public Point Result { get; }
public PickPointCompletedEventArgs(Point result)
{
this.Result = result;
}
}
Использование API
MainWindow. xaml.cs
partial class MainWindow : Window
{
private Utility Api { get; set; }
public MainWindow()
{
InitializeComponent();
this.Api = new Utility();
}
private void StartPickPoint_OnButtonClick(object sender, RoutedEventArgs e)
{
this.Api.InitializePickPointCompleted += RequestUserInput_OnInitializePickPointCompleted;
// Invoke API and continue to do something until the first step has completed.
// This is possible because the API will execute the operation on a background thread.
this.Api.BeginPickPoint();
}
private void RequestUserInput_OnInitializePickPointCompleted(object sender, EventArgs e)
{
// Cleanup
this.Api.InitializePickPointCompleted -= RequestUserInput_OnInitializePickPointCompleted;
// Communicate to the UI user that you are waiting for him to click on the screen
// e.g. by showing a Popup, dimming the screen or showing a dialog.
// Once the input is received the input event handler will invoke the API to complete the goal
MessageBox.Show("Please click the screen");
}
private void FinishPickPoint_OnGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Api.PickPointCompleted += ShowPoint_OnPickPointCompleted;
// Invoke API to complete the goal
// and continue to do something until the last step has completed
this.Api.EndPickPoint();
}
private void ShowPoint_OnPickPointCompleted(object sender, PickPointCompletedEventArgs e)
{
// Cleanup
this.Api.PickPointCompleted -= ShowPoint_OnPickPointCompleted;
// Get the result from the PickPointCompletedEventArgs instance
Point point = e.Result;
// Handle the result
MessageBox.Show(point.ToString());
}
}
MainWindow.xaml
<Window>
<Grid MouseLeftButtonUp="FinishPickPoint_OnGridMouseLeftButtonUp">
<Button Click="StartPickPoint_OnButtonClick" />
</Grid>
</Window>
Замечания
События, созданные в фоновом потоке, будут выполнять свои обработчики в той же теме. Для доступа к DispatcherObject
подобному элементу пользовательского интерфейса из обработчика, который выполняется в фоновом потоке, требуется поставить в очередь критическую операцию в Dispatcher
, используя либо Dispatcher.Invoke
, либо Dispatcher.InvokeAsync
, чтобы избежать межпоточных исключений.
Прочитайте замечания о DispatcherObject
, чтобы узнать больше об этом явлении, называемом сходством с диспетчером или сродством потока.
Некоторые мысли - ответьте на ваши комментарии
Поскольку вы подходили ко мне, чтобы найти «лучшее» решение для блокировки, на примере консольных приложений, я убедил вас, что ваше восприятие или точка зрения совершенно неверны.
"Рассмотрим консольное приложение с этими двумя строками кода.
var str = Console.ReadLine();
Console.WriteLine(str);
Что происходит, когда вы запускаете приложение в режиме отладки. Оно остановится на первой строке кода и заставит вас выполнить введите значение в пользовательском интерфейсе консоли, а затем после того, как вы введете что-то и нажмете клавишу Enter, он выполнит следующую строку и фактически напечатает то, что вы ввели. Я думал о том же поведении, но в приложении WPF. "
Консольное приложение - это нечто совершенно другое. Концепция потоков немного отличается. Консольные приложения не имеют GUI. Просто вход / выход / потоки ошибок. Вы не можете сравнить архитектуру консольного приложения с многофункциональным GUI приложением. Это не сработает. Вы действительно должны понять и принять это.
Также не обманывайтесь внешностью . Вы знаете, что происходит внутри Console.ReadLine
? Как это реализовано ? Он блокирует основной поток и параллельно читает ввод? Или это просто опрос?
Вот оригинальная реализация Console.ReadLine
:
public virtual String ReadLine()
{
StringBuilder sb = new StringBuilder();
while (true)
{
int ch = Read();
if (ch == -1)
break;
if (ch == '\r' || ch == '\n')
{
if (ch == '\r' && Peek() == '\n')
Read();
return sb.ToString();
}
sb.Append((char)ch);
}
if (sb.Length > 0)
return sb.ToString();
return null;
}
Как вы можете видеть, это простая синхронная операция. Он опрашивает пользовательский ввод в «бесконечном» l oop. Не блокируйте и не продолжайте magi c.
WPF построен вокруг потока рендеринга и потока пользовательского интерфейса. Эти потоки продолжают вращаться всегда , чтобы взаимодействовать с ОС, например, обрабатывая ввод данных пользователем - поддерживая приложение отзывчивым . Вы никогда не хотите приостанавливать / блокировать этот поток, поскольку он не позволит каркасу выполнять важную фоновую работу, например, реагирование на события мыши - вы не хотите, чтобы мышь зависала:
wait = блокировка потока = неотзывчивость = плохой UX = раздраженные пользователи / клиенты = проблемы в офисе.
Иногда поток приложения требует ожидания ввода или выполнения подпрограммы. Но мы не хотим блокировать основной поток.
Именно поэтому люди изобрели сложные модели асинхронного программирования, чтобы позволить ожидание, не блокируя основной поток и не заставляя разработчика писать сложный и ошибочный многопоточный код.
Каждая современная прикладная среда предлагает асинхронные операции или модель асинхронного программирования, позволяющую разрабатывать простой и эффективный код.
Тот факт, что вы изо всех сил пытаетесь противостоять модели асинхронного программирования, показывает мне некоторое непонимание. Каждый современный разработчик предпочитает асинхронный API, а не синхронный. Ни один серьезный разработчик не захочет использовать ключевое слово await
или объявить свой метод async
. Никто. Вы первый раз сталкиваетесь с теми, кто жалуется на асинхронные API и считает их неудобными для использования.
Если бы я проверил вашу платформу, которая предназначена для решения проблем, связанных с пользовательским интерфейсом, или упростил бы задачи, связанные с пользовательским интерфейсом, я бы ожидал, что будет асинхронным - полностью.
Связанный с пользовательским интерфейсом API, который не является асинхронным, является ненужным, так как это усложнит мой стиль программирования, поэтому мой код становится более подверженным ошибкам и сложным в обслуживании.
Другая точка зрения: когда вы признаете, что ожидание блокирует поток пользовательского интерфейса, создается очень плохой и нежелательный пользовательский интерфейс, поскольку пользовательский интерфейс будет зависать до тех пор, пока не закончится ожидание, теперь, когда вы понимаете это, зачем вам предлагать модель API или плагина, которая побуждает разработчика сделать именно это - реализовать ожидание?
Вы не знаете, что будет делать сторонний плагин и сколько времени займет процедура до ее завершения. Это просто плохой дизайн API. Когда ваш API работает в потоке пользовательского интерфейса, то вызывающая сторона вашего API должна иметь возможность делать неблокирующие вызовы к нему.
Если вы отрицаете единственное дешевое или изящное решение, тогда используйте подход, основанный на событиях, как показано в моем примере.
Он делает то, что вы хотите: запустить процедуру - ждать ввода пользователя - продолжить выполнение - Выполнено sh цель.
Я действительно несколько раз пытался объяснить, почему ожидание / блокировка - это плохой дизайн приложения. Опять же, вы не можете сравнить консольный пользовательский интерфейс с богатым графическим пользовательским интерфейсом, где, например, одна только обработка ввода намного сложнее, чем просто прослушивание входного потока. Я действительно не знаю ваш уровень опыта и где вы начали, но вы должны начать использовать модель асинхронного программирования. Я не знаю причину, почему вы пытаетесь избежать этого. Но это совсем не мудро.
Сегодня модели асинхронного программирования применяются везде, на любой платформе, компиляторе, в любой среде, браузере, сервере, настольном компьютере, базе данных - везде. Модель, управляемая событиями, позволяет достичь той же цели, но она менее удобна в использовании (подписка / отмена подписки на события и от них, чтение документов (если есть документы), чтобы узнать о событиях), опираясь на фоновые потоки. Управляемый событиями старомоден и должен использоваться только тогда, когда асинхронные библиотеки недоступны или неприменимы.
В качестве дополнительного примечания:. NET Framwork (. NET Standard) предлагает TaskCompletionSource
(среди прочих целей), чтобы обеспечить простой способ преобразования существующего Четный интерфейс API в асинхронном API.
«Я видел точное поведение в Autodesk Revit.»
Поведение (то, что вы испытываете или наблюдаете) сильно отличается от того, как этот опыт реализован. Две разные вещи. Ваш Autodesk, скорее всего, использует асинхронные библиотеки или языковые функции или какой-либо другой механизм потоков. И это также связано с контекстом. Когда ваш метод выполняется в фоновом потоке, разработчик может заблокировать этот поток. У него либо есть очень веская причина, либо он сделал неправильный выбор дизайна. Вы на ложном пути;) Блокировка нехороша.
(Является ли исходный код Autodesk открытым исходным кодом? Или как вы знаете, как он реализован?)
Я не хочу обидеть ты, пожалуйста, поверь мне. Но, пожалуйста, пересмотрите возможность реализации вашего API асинхронно. Только в вашей голове разработчики не любят использовать async / await. Вы явно ошиблись. И забудьте об этом аргументе консольного приложения - это чепуха;)
API, связанный с пользовательским интерфейсом ДОЛЖЕН по возможности использовать async / await. В противном случае вы оставляете всю работу по написанию неблокирующего кода клиенту вашего API. Вы бы заставили меня обернуть каждый вызов вашего API в фоновый поток. Или использовать менее удобную обработку событий. Поверьте мне - каждый разработчик скорее украшает своих членов async
, чем выполняет обработку событий. Каждый раз, когда вы используете события, вы можете рисковать потенциальной утечкой памяти - это зависит от некоторых обстоятельств, но риск реален и не редок при небрежном программировании.
Я действительно надеюсь, что вы понимаете, почему блокировка плохая. Я действительно надеюсь, что вы решите использовать async / await для написания современного асинхронного API. Тем не менее, я показал вам очень распространенный способ ожидания неблокирования, используя события, хотя я призываю вас использовать async / await.
"API позволит программисту иметь доступ к пользовательскому интерфейсу и т. д. c. Теперь предположим, что программист хочет разработать надстройку, чтобы при нажатии кнопки конечному пользователю предлагалось выбрать точку в пользовательском интерфейсе "
Если вы не Если вы хотите, чтобы плагин имел прямой доступ к элементам пользовательского интерфейса, вы должны предоставить интерфейс для делегирования событий или предоставления внутренних компонентов через абстрагированные объекты.
Внутренний API-интерфейс будет подписываться на события пользовательского интерфейса от имени надстройки, а затем делегирует событие, выставляя соответствующее событие «оболочки» клиенту API. Ваш API должен предлагать несколько хуков, где надстройка может подключаться для доступа к указанным c компонентам приложения. API плагина действует как адаптер или фасад для предоставления доступа к внешним устройствам.
Для обеспечения некоторой степени изоляции.
Посмотрите, как Visual Studio управляет плагинами или позволяет нам их реализовывать. Представьте, что вы хотите написать плагин для Visual Studio, и поинтересуйтесь, как это сделать. Вы поймете, что Visual Studio предоставляет свои внутренние возможности через интерфейс или API. Например, вы можете манипулировать редактором кода или получать информацию о содержимом редактора без real доступа к нему.