Как заблокировать поток кода, пока событие не будет запущено в C# - PullRequest
10 голосов
/ 13 апреля 2020

Здесь у нас есть Grid с Button. Когда пользователь нажимает кнопку, выполняется метод в классе Utility, который заставляет приложение получать щелчок по Grid. Здесь поток кода должен остановиться и не продолжаться до тех пор, пока пользователь не нажмет на Grid.

У меня уже был похожий вопрос:

Подождите, пока пользователь не нажмет C# WPF

В этом вопросе я получил ответ, используя async / await, который работает, но, поскольку я собираюсь использовать его как часть API, я не хочу использовать async / await, так как тогда потребители должны будут пометить свои методы с помощью asyn c, который я не хочу.

Как мне написать 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)
    {

    }
}

Ответы [ 7 ]

9 голосов
/ 17 апреля 2020

«Как заблокировать поток кода, пока событие не будет запущено?»

Ваш подход неверен. Управляемый событиями не означает блокирование и ожидание события. Вы никогда не ждете, по крайней мере, вы всегда стараетесь избежать этого. Ожидание - это бесполезная трата ресурсов, блокировка потоков и, возможно, введение риска тупика или зомбирования потока ie (в случае, если сигнал освобождения никогда не поднимается).
Должно быть ясно, что блокировка потока на wait для события - это анти-шаблон, поскольку он противоречит идее события.

У вас обычно есть два (современных) варианта: реализовать асинхронный API или API, управляемый событиями. Поскольку вы не хотите реализовывать свой API-интерфейс асинхронным, у вас остается API, управляемый событиями.

Ключ API, управляемого событиями, заключается в том, что вместо того, чтобы заставлять вызывающего абонента синхронно ожидать результат или опросить результат, вы позволяете вызывающему продолжить и отправлять ему уведомление, как только результат будет готов. или операция завершена. Между тем, вызывающая сторона может продолжать выполнять другие операции.

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

Поскольку вы не предоставили достаточно информации о том, что вы на самом деле пытаетесь сделать, что на самом деле делает Utility.PickPoint() и каков результат задания или почему пользователь должен нажать на «Сетка», Я не могу предложить вам лучшее решение. Я просто могу предложить общую схему того, как реализовать ваше требование.

Ваш поток или цель, очевидно, разделены по крайней мере на два шага, чтобы сделать его последовательностью операций:

  1. Выполнить операцию 1, когда пользователь нажимает кнопку
  2. Выполнить операцию 2 (продолжить / завершить операцию 1), когда пользователь нажимает на Grid

как минимум с двумя ограничениями :

  1. Необязательно: последовательность должна быть завершена, прежде чем клиенту API разрешат ее повторить. Последовательность завершается после завершения операции 2.
  2. Операция 1 всегда выполняется перед операцией 2. Операция 1 запускает последовательность.
  3. Операция 1 должна завершиться, прежде чем клиенту API будет разрешено выполнить операцию 2

Это требует двух уведомлений для клиента API, чтобы разрешить неблокирующее взаимодействие:

  1. Операция 1 завершена (или требуется взаимодействие)
  2. Операция 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 доступа к нему.

5 голосов
/ 15 апреля 2020

Лично я думаю, что все это слишком усложняют, но, возможно, я не до конца понимаю причину, по которой это нужно сделать определенным образом, но кажется, что здесь можно использовать простую проверку bool.

Прежде всего, сделайте вашу сетку тестируемой на попадание, установив свойства Background и IsHitTestVisible, иначе она даже не будет захватывать щелчки мыши.

<grid MouseLeftButtonUp="Grid_MouseLeftButtonUp" IsHitTestVisible="True" Background="Transparent">

Далее создайте значение bool это может хранить, должно ли произойти событие "GridClick". Если щелкнуть сетку, проверьте это значение и выполните выполнение из события щелчка сетки, если оно ожидает щелчка.

Пример:

bool awaitingClick = false;


private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   awaitingClick=true;
}

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{     
     //Stop here if the program shouldn't do anything when grid is clicked
     if (!awaitingClick) { return; } 

     //Run event
     var point = Utility.PickPoint(View);
     MessageBox.Show(point.ToString());

     awaitingClick=false;//Reset
}
2 голосов
/ 20 апреля 2020

Технически это возможно с AutoResetEvent и без async/await, но есть существенный недостаток:

public static Point PickPoint(Grid grid)
{
    var pointPicked = new AutoResetEvent(false);
    grid.MouseLeftButtonUp += (s, e) => 
    {
        // do whatever after the grid is clicked

        // signal the end of waiting
        pointPicked.Set();
    };

    // code flow will stop here and wait until the grid is clicked
    pointPicked.WaitOne();
    // return something...
}

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

Потребители метода должны вызывать его в другом потоке, чтобы предотвратить взаимные блокировки. Если это можно гарантировать, это нормально. В противном случае вам нужно вызвать метод следующим образом:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    // here I used ThreadPool, but you may use other means to run on another thread
    ThreadPool.QueueUserWorkItem(new WaitCallback(Capture));
}

private void Capture(object state)
{
    // 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());
}

Это может вызвать больше проблем у пользователей вашего API, за исключением того, что они использовали для управления своими собственными потоками. Вот почему async/await был изобретен.

2 голосов
/ 14 апреля 2020

Вы можете блокировать асинхронно, используя SemaphoreSlim:

public partial class MainWindow : Window, IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Utility.PickPoint(View);

        // 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.
        await _semaphoreSlim.WaitAsync();

        MessageBox.Show(point.ToString());
    }

    private void View_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //click on grid detected....
        _semaphoreSlim.Release();
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        Dispose();
    }

    public void Dispose() => _semaphoreSlim.Dispose();
}

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

2 голосов
/ 13 апреля 2020

Я попробовал несколько вещей, но я не могу сделать это без async/await. Потому что, если мы не используем его, это вызывает DeadLock, или пользовательский интерфейс блокируется, и тогда мы можем принять Grid_Click ввод.

private async void ToolBtn_OnClick(object sender, RoutedEventArgs e)
{
    var senderBtn = sender as Button;
    senderBtn.IsEnabled = false;

    var response = await Utility.PickPoint(myGrid);
    MessageBox.Show(response.ToString());
    senderBtn.IsEnabled = true;
}  

public static class Utility
{
    private static TaskCompletionSource<bool> tcs;
    private static Point _point = new Point();

    public static async Task<Point> PickPoint(Grid grid)
    {
        tcs = new TaskCompletionSource<bool>();
        _point = new Point();

        grid.MouseLeftButtonUp += GridOnMouseLeftButtonUp;


        await tcs.Task;

        grid.MouseLeftButtonUp -= GridOnMouseLeftButtonUp;
        return _point;
    }


    private static void GridOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        // do something here ....
        _point = new Point { X = 23, Y = 34 };
        // do something here ....

        tcs.SetResult(true); // as soon its set it will go back

    }
}
0 голосов
/ 20 апреля 2020

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

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Utility.PickPoint(View, (x) => MessageBox.Show(x.ToString()));
    }
}

public static class Utility
{
    private static Action<Point> work;

    public static void PickPoint(Grid grid, Action<Point> work)
    {
        if (Utility.work == null)
        {
            grid.PreviewMouseLeftButtonUp += Grid_PreviewMouseLeftButtonUp;
            Utility.work = work;
        }
    }

    private static void Grid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;
        work.Invoke(e.GetPosition(grid));
        grid.PreviewMouseLeftButtonUp -= Grid_PreviewMouseLeftButtonUp;
        Utility.work = null;
    }
}   

Но если вы хотите заблокировать поток пользовательского интерфейса или «поток кода», ответ будет, что это невозможно. Потому что, если поток пользовательского интерфейса был заблокирован, дальнейший ввод не может быть получен.
Поскольку вы как бы упоминали о консольном приложении, я просто делаю несколько простых объяснений.
Когда вы запускаете консольное приложение или вызываете AllocConsole из процесс, который не был подключен к какой-либо консоли (окну), conhost.exe, который может предоставить консоль (окно), будет выполнен, и процесс консоли приложения или вызывающего процесса будет присоединен к консоли (окну).
Таким образом, любой код вы пишете, что это может блокировать поток вызывающего, например Console.ReadKey, не будет блокировать поток пользовательского интерфейса окна консоли, и это причина, по которой консольное приложение ожидает вашего ввода, но все еще может реагировать на другой ввод, например, щелчок мышью.

0 голосов
/ 20 апреля 2020

Я думаю, что проблема в самом дизайне. Если ваш API работает с определенным элементом, его следует использовать в обработчике событий этого самого элемента, а не другого элемента.

Например, здесь мы хотим получить позицию события click в таблице. API необходимо использовать в обработчике событий, связанном с событием в элементе Grid, а не в элементе button.

Теперь, если требуется обрабатывать нажатие на Grid только после того, как мы щелкнули Button, тогда ответственность Button будет состоять в том, чтобы добавить обработчик событий в Grid, а событие click в Grid отобразит окно сообщения и удалит этот обработчик событий, добавленный кнопкой, чтобы он больше не вызывал срабатывание после этого нажатия. ... (нет необходимости блокировать поток пользовательского интерфейса)

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

...