Здесь у нас есть Grid
с собой Button
. Когда пользователь нажимает кнопку, выполняется метод в классе Utility, который заставляет приложение получать щелчок по Grid. Поток кода должен останавливаться здесь и не продолжаться до тех пор, пока пользователь не нажмет на Grid
.
У меня был подобный вопрос, прежде чем здесь:
Подождите, пока пользователь не нажмет C # WPF
В этом вопросе я получил ответ, используя async / await, который работает, но так как я собираюсь использовать его как часть API, я не хочу использовать async / await, так как тогда потребители должны будут пометить свои методы с помощью асинхронность которой я не хочу.
Как мне написать Utility.PickPoint(Grid grid)
метод для достижения этой цели?
Я видел это, которое может помочь, но не совсем понял, как это применить здесь, если честно:
Блокировка до завершения события
Считайте это чем-то вроде метода Console.ReadKey () в консольном приложении. Когда мы вызываем этот метод, поток кода останавливается, пока мы не введем какое-либо значение. Отладчик не продолжается, пока мы не введем что-либо. Я хочу точное поведение для метода PickPoint (). Поток кода будет остановлен, пока пользователь не нажмет на Grid.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid x:Name="View" Background="Green"/>
<Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// do not continue the code flow until the user has clicked on the grid.
// so when we debug, the code flow will literally stop here.
var point = Utility.PickPoint(View);
MessageBox.Show(point.ToString());
}
}
public static class Utility
{
public static Point PickPoint(Grid grid)
{
}
}
источник
Aync/Await
как выполнить операцию А и сохранить эту операцию СОСТОЯНИЕ. Теперь вы хотите, чтобы пользователь щелкнул «Сетка». Поэтому, если пользователь щелкает «Сетка», вы проверяете состояние, если оно истинно, а затем выполняете свою операцию, просто делая то, что вы хотите ??AutoResetEvent
это не то, что вы хотите?var point = Utility.PickPoint(Grid grid);
в метод Grid Click? сделать какую-то операцию и вернуть ответ?Ответы:
Ваш подход неверен. Управляемый событиями не означает блокирование и ожидание события. Вы никогда не ждете, по крайней мере, вы всегда стараетесь избежать этого. Ожидание - это бесполезная трата ресурсов, блокировка потоков и, возможно, введение риска тупика или потока зомби (в случае, если сигнал освобождения никогда не поднимается).
Должно быть ясно, что блокировка потока для ожидания события является анти-паттерном, поскольку противоречит идее события.
Обычно у вас есть два (современных) варианта: реализовать асинхронный API или API, управляемый событиями. Поскольку вы не хотите реализовывать свой API-интерфейс асинхронным, у вас остается API, управляемый событиями.
Ключ API, управляемого событиями, заключается в том, что вместо того, чтобы заставлять вызывающего абонента синхронно ожидать результата или опросить результат, вы позволяете вызывающему продолжить и отправлять ему уведомление, как только результат будет готов или операция завершена. Между тем, вызывающая сторона может продолжать выполнять другие операции.
Если рассматривать проблему с точки зрения многопоточности, то управляемый событиями API позволяет вызывающему потоку, например потоку пользовательского интерфейса, который выполняет обработчик события кнопки, быть свободным, чтобы продолжать обрабатывать, например, другие связанные с пользовательским интерфейсом операции, такие как рендеринг элементов пользовательского интерфейса. или обрабатывать пользовательский ввод, такой как движение мыши и нажатие клавиш. API, управляемый событиями, имеет тот же эффект или цель, что и асинхронный API, хотя это гораздо менее удобно.
Поскольку вы не предоставили достаточно информации о том, что вы на самом деле пытаетесь сделать, что
Utility.PickPoint()
на самом деле делает и каков результат задания или почему пользователь должен нажать на «Сетка», я не могу предложить вам лучшее решение , Я просто могу предложить общую схему того, как реализовать ваше требование.Ваш поток или цель, очевидно, разделены по крайней мере на два шага, чтобы сделать это последовательностью операций:
Grid
по крайней мере с двумя ограничениями:
Это требует от двух уведомлений для клиента API, чтобы разрешить неблокирующее взаимодействие:
Вы должны позволить своему API реализовать это поведение и ограничения, предоставив два открытых метода и два открытых события.
Реализация / рефакторинг Utility API
Utility.cs
PickPointCompletedEventArgs.cs
Используйте API
MainWindow.xaml.cs
MainWindow.xaml
замечания
События, созданные в фоновом потоке, будут выполнять свои обработчики в том же потоке. Доступ к
DispatcherObject
как элемент пользовательского интерфейса от обработчика, который выполняется в фоновом потоке, требуется критическая операция должна быть поставлена в очередь кDispatcher
используя либоDispatcher.Invoke
или ,Dispatcher.InvokeAsync
чтобы избежать исключений кросса-нити.Прочитайте замечания о том,
DispatcherObject
чтобы узнать больше об этом явлении, называемом сходством с диспетчером или сродством потокаНекоторые мысли - ответьте на ваши комментарии
Поскольку вы подходили ко мне, чтобы найти «лучшее» решение для блокировки, на примере консольных приложений, я убедил вас, что ваше восприятие или точка зрения совершенно неверны.
Консольное приложение - это нечто совершенно другое. Концепция потоков немного отличается. Консольные приложения не имеют графического интерфейса. Просто вход / выход / потоки ошибок. Вы не можете сравнить архитектуру консольного приложения с многофункциональным графическим приложением. Это не сработает. Вы действительно должны понять и принять это.
Также не обманывайтесь внешностью . Вы знаете, что происходит внутри
Console.ReadLine
? Как это реализовано ? Он блокирует основной поток и параллельно читает ввод? Или это просто опрос?Вот оригинальная реализация
Console.ReadLine
:Как видите, это простая синхронная операция. Опрашивает пользовательский ввод в «бесконечном» цикле. Нет магического блока и продолжай.
WPF построен вокруг потока рендеринга и потока пользовательского интерфейса. Эти потоки постоянно вращаются, чтобы обмениваться данными с ОС, например, обрабатывать ввод данных пользователем, поддерживая отзывчивость приложения . Вы никогда не захотите приостанавливать / блокировать этот поток, поскольку он не позволит каркасу выполнять важную фоновую работу, например, реагирование на события мыши - вы не хотите, чтобы мышь зависала:
ожидание = блокировка потока = неотзывчивость = плохой UX = раздраженные пользователи / клиенты = проблемы в офисе.
Иногда поток приложения требует ожидания ввода или выполнения подпрограммы. Но мы не хотим блокировать основной поток.
Вот почему люди изобрели сложные модели асинхронного программирования, чтобы позволить ожидание, не блокируя основной поток и не заставляя разработчика писать сложный и ошибочный многопоточный код.
Каждая современная прикладная среда предлагает асинхронные операции или модель асинхронного программирования, позволяющую разрабатывать простой и эффективный код.
Тот факт, что вы изо всех сил пытаетесь противостоять модели асинхронного программирования, показывает некоторое отсутствие понимания для меня. Каждый современный разработчик предпочитает асинхронный API, а не синхронный. Ни один серьезный разработчик не хочет использовать
await
ключевое слово или объявлять его методasync
. Никто. Вы первый раз сталкиваетесь с теми, кто жалуется на асинхронные API и считает их неудобными для использования.Если бы я проверил вашу платформу, которая предназначена для решения проблем, связанных с пользовательским интерфейсом, или облегчения задач, связанных с пользовательским интерфейсом, я бы ожидал, что она будет асинхронной - в любом случае.
API, связанный с пользовательским интерфейсом, который не является асинхронным, является пустой тратой, поскольку это усложнит мой стиль программирования, поэтому мой код становится более подверженным ошибкам и сложным в обслуживании.
Другая точка зрения: когда вы признаете, что ожидание блокирует поток пользовательского интерфейса, создает очень плохой и нежелательный пользовательский интерфейс, поскольку пользовательский интерфейс будет зависать до тех пор, пока не закончится ожидание, теперь, когда вы это понимаете, зачем вам предлагать модель API или плагина, которая побуждает разработчика сделать именно это - реализовать ожидание?
Вы не знаете, что будет делать сторонний плагин и сколько времени займет выполнение процедуры. Это просто плохой дизайн API. Когда ваш API работает в потоке пользовательского интерфейса, то вызывающая сторона вашего API должна иметь возможность делать неблокирующие вызовы к нему.
Если вы отрицаете единственное дешевое или изящное решение, используйте подход, основанный на событиях, как показано в моем примере.
Он делает то, что вы хотите: запустить процедуру - ждать ввода пользователя - продолжить выполнение - достичь цели.
Я действительно несколько раз пытался объяснить, почему ожидание / блокировка - плохой дизайн приложения. Опять же, вы не можете сравнить консольный пользовательский интерфейс с богатым графическим пользовательским интерфейсом, где, например, одна только обработка ввода намного сложнее, чем просто прослушивание входного потока. Я действительно не знаю ваш уровень опыта и где вы начали, но вы должны начать использовать модель асинхронного программирования. Я не знаю причину, почему вы пытаетесь избежать этого. Но это совсем не мудро.
Сегодня модели асинхронного программирования применяются везде, на любой платформе, компиляторе, в любой среде, браузере, сервере, настольном компьютере, базе данных - везде. Модель, управляемая событиями, позволяет достичь той же цели, но она менее удобна в использовании (подписка / отмена подписки на события и от них, чтение документов (если есть документы), чтобы узнать о событиях), опираясь на фоновые потоки. Управляемый событиями старомоден и должен использоваться только тогда, когда асинхронные библиотеки недоступны или неприменимы.
В качестве дополнительного примечания: .NET Framwork (.NET Standard) предлагает
TaskCompletionSource
(среди прочих целей) простой способ преобразования существующего ровного API в асинхронный API.Поведение (то, что вы испытываете или наблюдаете) сильно отличается от того, как этот опыт реализован. Две разные вещи. Ваш Autodesk, скорее всего, использует асинхронные библиотеки или языковые функции или какой-либо другой механизм потоков. И это также связано с контекстом. Когда ваш метод выполняется в фоновом потоке, разработчик может заблокировать этот поток. У него либо есть очень веская причина, либо он сделал неправильный выбор дизайна. Вы совершенно не на том пути;) Блокировка не очень хорошая.
(Является ли исходный код Autodesk открытым исходным кодом? Или как вы знаете, как он реализован?)
Я не хочу тебя обидеть, пожалуйста, поверь мне. Но, пожалуйста, пересмотрите возможность реализации вашего API асинхронно. Только в вашей голове разработчики не любят использовать async / await. Вы явно ошиблись. И забыть об этом аргументе консольного приложения - это нонсенс;)
API, связанный с пользовательским интерфейсом, ДОЛЖЕН использовать async / await всякий раз, когда это возможно. В противном случае вы оставляете всю работу по написанию неблокирующего кода клиенту вашего API. Вы бы заставили меня обернуть каждый вызов вашего API в фоновый поток. Или использовать менее удобную обработку событий. Поверьте мне - каждый разработчик скорее украшает своих участников
async
, чем занимается обработкой событий. Каждый раз, когда вы используете события, вы можете рисковать потенциальной утечкой памяти - это зависит от некоторых обстоятельств, но риск реален и не редок при небрежном программировании.Я действительно надеюсь, что вы понимаете, почему блокировка это плохо. Я действительно надеюсь, что вы решите использовать async / await для написания современного асинхронного API. Тем не менее, я показал вам очень распространенный способ ожидания неблокирования, используя события, хотя я призываю вас использовать async / await.
Если вы не хотите, чтобы плагин имел прямой доступ к элементам пользовательского интерфейса, вы должны предоставить интерфейс для делегирования событий или предоставления внутренних компонентов через абстрагированные объекты.
Интерфейс API будет подписываться на события пользовательского интерфейса от имени надстройки, а затем делегирует это событие, предоставляя соответствующее событие «оболочки» клиенту API. Ваш API должен предлагать несколько ловушек, с помощью которых надстройка может подключаться для доступа к определенным компонентам приложения. API плагинов действует как адаптер или фасад, предоставляя внешним доступам к внутренним объектам.
Чтобы позволить степень изоляции.
Посмотрите, как Visual Studio управляет плагинами или позволяет нам их реализовывать. Представьте, что вы хотите написать плагин для Visual Studio, и поинтересуйтесь, как это сделать. Вы поймете, что Visual Studio предоставляет свои внутренние возможности через интерфейс или API. Например, вы можете манипулировать редактором кода или получать информацию о содержимом редактора без реального доступа к нему.
источник
var str = Console.ReadLine(); Console.WriteLine(str);
Что происходит, когда вы запускаете приложение в режиме отладки. Он остановится на первой строке кода и заставит вас ввести значение в пользовательском интерфейсе консоли, а затем, после того как вы введете что-то и нажмете Enter, выполнит следующую строку и фактически напечатает то, что вы ввели. Я думал о том же поведении, но в приложении WPF.Лично я думаю, что все это слишком усложняют, но, возможно, я не до конца понимаю причину, по которой это нужно делать определенным образом, но кажется, что здесь можно использовать простую проверку bool.
В первую очередь, сделать сетку хит-проверяемым путем установки
Background
иIsHitTestVisible
свойства, или иначе это не будет даже захвата щелчков мыши.Затем создайте значение bool, которое может хранить, должно ли происходить событие «GridClick». Когда щелкается сетка, проверьте это значение и выполните выполнение из события щелчка сетки, если оно ожидает щелчка.
Пример:
источник
Я попробовал несколько вещей, но я не могу сделать это без
async/await
. Потому что, если мы не используем его, это вызывает,DeadLock
или пользовательский интерфейс заблокирован, и тогда мы можем приниматьGrid_Click
данные.источник
Вы можете заблокировать асинхронно, используя
SemaphoreSlim
:Вы не можете и не хотите блокировать поток диспетчера синхронно, потому что тогда он никогда не сможет обрабатывать щелчок
Grid
, т. Е. Он не может одновременно блокироваться и обрабатывать события.источник
Console.Readline
блокирует , т.е. не возвращает, пока не будет прочитана строка. ВашPickPoint
метод не делает. Возвращается немедленно. Это может потенциально заблокировать, но тогда вы не сможете обрабатывать ввод пользовательского интерфейса в то же время, как я написал в своем ответе. Другими словами, вам придется обрабатывать щелчок внутри метода, чтобы получить то же поведение.PickPoint
который обрабатывает события мыши. Я не вижу, куда вы идете с этим?Технически это возможно с
AutoResetEvent
и безasync/await
, но есть существенный недостаток:Недостаток: если вы вызываете этот метод непосредственно в обработчике события кнопки, как это делает ваш пример кода, произойдет взаимоблокировка, и вы увидите, что приложение перестает отвечать на запросы. Поскольку вы используете единственный поток пользовательского интерфейса для ожидания щелчка пользователя, он не может реагировать ни на какие действия пользователя, включая щелчок пользователя в сетке.
Потребители метода должны вызывать его в другом потоке, чтобы предотвратить взаимные блокировки. Если это можно гарантировать, это нормально. В противном случае вам нужно вызвать метод следующим образом:
Это может вызвать больше проблем у пользователей вашего API, за исключением того, что они использовали для управления своими собственными потоками. Вот почему
async/await
был изобретен.источник
Я думаю, что проблема заключается в самом дизайне. Если ваш API работает с определенным элементом, его следует использовать в обработчике событий этого самого элемента, а не с другим элементом.
Например, здесь мы хотим получить позицию события click в Grid, API нужно использовать в обработчике событий, связанном с событием в элементе Grid, а не в элементе button.
Теперь, если требование обрабатывать нажатие на сетку только после того, как мы нажали кнопку, ответственность за кнопку будет состоять в том, чтобы добавить обработчик событий в сетку, и событие щелчка в сетке отобразит окно сообщения и удалит этот обработчик событий добавлен кнопкой, чтобы он больше не срабатывал после этого нажатия ... (нет необходимости блокировать поток пользовательского интерфейса)
Просто чтобы сказать, что если вы заблокируете поток пользовательского интерфейса при нажатии кнопки, я не думаю, что поток пользовательского интерфейса сможет инициировать событие click в Grid впоследствии.
источник
Прежде всего, поток пользовательского интерфейса не может быть заблокирован так же, как ответ, который вы получили из своего раннего вопроса.
Если вы можете согласиться с этим, то избежать асинхронного ожидания / ожидания, чтобы ваш клиент делал меньше изменений, выполнимо и даже не нужно многопоточность.
Но если вы хотите заблокировать поток пользовательского интерфейса или «поток кода», ответ будет, что это невозможно. Потому что, если поток пользовательского интерфейса был заблокирован, дальнейший ввод не может быть получен.
Поскольку вы как бы упоминали о консольном приложении, я просто приведу несколько простых объяснений.
Когда вы запускаете консольное приложение или вызываете
AllocConsole
процесс, который не подключен к какой-либо консоли (окну), будет запущен conhost.exe, который может предоставить консоль (окно), и консольное приложение или процесс вызывающего абонента будет присоединен к консоли ( окно).Таким образом, любой код, который вы пишете, который может блокировать поток вызывающей стороны, например
Console.ReadKey
, не будет блокировать поток пользовательского интерфейса консольного окна, любой причина, по которой консольное приложение ожидает вашего ввода, но все еще может реагировать на другой ввод, такой как щелчок мышью.источник