C #에서 이벤트가 발생할 때까지 코드 흐름을 차단하는 방법


9

여기에 Grid와가 Button있습니다. 사용자가 버튼을 클릭하면 Utility 클래스의 메소드가 실행되어 응용 프로그램이 그리드를 클릭하도록합니다. 코드 흐름은 여기서 중지하고 사용자가를 클릭 할 때까지 계속 진행하지 않아야합니다 Grid.

나는 여기 전에 비슷한 질문을했다.

사용자가 C # WPF를 클릭 할 때까지 기다리십시오.

이 질문에서 async / await를 사용하여 답변을 얻었지만 API의 일부로 사용하려고하기 때문에 소비자가 다음과 같이 메소드를 표시해야하기 때문에 async / await를 사용하고 싶지 않습니다. 내가 원하지 않는 비동기.

Utility.PickPoint(Grid grid)이 목표를 달성하기위한 방법을 어떻게 작성 합니까?

나는 이것이 도움이 될 수 있지만 정직하기 위해 여기에 적용하는 것을 완전히 이해하지는 못했습니다.

이벤트가 완료 될 때까지 차단

콘솔 응용 프로그램의 Console.ReadKey () 메서드와 같은 것으로 생각하십시오. 이 메서드를 호출하면 값을 입력 할 때까지 코드 흐름이 중지됩니다. 우리가 무언가를 입력 할 때까지 디버거는 계속되지 않습니다. PickPoint () 메서드에 대한 정확한 동작을 원합니다. 사용자가 그리드를 클릭 할 때까지 코드 흐름이 중지됩니다.

<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작업 A를 수행하고 해당 작업을 저장하는 방법입니다. 이제 상태를 사용자가 그리드를 클릭해야합니다.
Rao Hammas Hussain

@RaoHammasHussain 도움이 될만한 링크로 내 질문을 업데이트했습니다. 유틸리티 메소드는 최종 사용자가 화면을 클릭하도록 요청할 때마다 API 사용자가 호출하는 API의 일부입니다. 일반 Windows 응용 프로그램 또는 Console.Readline () 메서드의 텍스트 프롬프트 창과 같은 것으로 생각하십시오. 이 경우 사용자가 무언가를 입력 할 때까지 코드 흐름이 중지됩니다. 이제는 정확한 것을 원하지만 이번에는 사용자가 화면을 클릭합니다.
Vahid

AutoResetEvent당신이 원하는 것이 아닙니다?
Rao Hammas Hussain

@RaoHammasHussain 나는 그렇게 생각하지만 실제로 그것을 사용하는 방법을 모른다.
Vahid

WAIT STATE를 의도적으로 구현하는 것과 같습니다. 정말 필요한가요? cuz 당신은 그냥 var point = Utility.PickPoint(Grid grid);그리드 클릭 방법에 넣을 수 없습니다 ? 일부 작업을 수행하고 응답을 반환합니까?
Rao Hammas Hussain

답변:


7

"이벤트가 발생할 때까지 코드 흐름을 차단하는 방법은 무엇입니까?"

당신은 접근이 잘못되었습니다. 이벤트 중심은 이벤트를 차단하고 기다리는 것을 의미하지 않습니다. 당신은 기다릴 필요가 없습니다, 적어도 당신은 항상 그것을 피하려고 열심히 노력합니다. 대기 중은 리소스 낭비, 스레드 차단 및 교착 상태 또는 좀비 스레드 위험이 발생할 수 있습니다 (신호가 발생하지 않는 경우).
이벤트를 기다리기 위해 스레드를 차단하는 것은 이벤트 아이디어와 모순되므로 안티 패턴입니다.

일반적으로 비동기 API 또는 이벤트 중심 API를 구현하는 두 가지 (현대) 옵션이 있습니다. API 비동기를 구현하고 싶지 않기 때문에 이벤트 중심 API가 남아 있습니다.

이벤트 중심 API의 핵심은 호출자가 결과를 동 기적으로 기다리거나 결과를 폴링하는 대신 결과가 준비되거나 작업이 완료되면 호출자가 계속해서 알림을 보내도록하는 것입니다. 한편, 발신자는 다른 작업을 계속 실행할 수 있습니다.

스레딩 관점에서 문제를 볼 때 이벤트 중심 API를 사용하면 버튼의 이벤트 핸들러를 실행하는 UI 스레드와 같은 호출 스레드가 UI 요소 렌더링 또는 처리와 같은 UI 관련 작업을 계속 처리 할 수 ​​있습니다. 마우스 이동 및 키 누름과 같은 사용자 입력. 비록 덜 편리하지만 비동기 API와 같은 효과.

Utility.PickPoint()실제로 수행 하려는 작업, 실제로 수행중인 작업 및 작업 결과 및 사용자가 그리드를 클릭해야하는 이유에 대한 자세한 정보를 제공하지 않았으므로 더 나은 솔루션을 제공 할 수 없습니다 . 요구 사항을 구현하는 방법에 대한 일반적인 패턴을 제공 할 수 있습니다.

당신의 흐름이나 목표는 분명히 일련의 작업으로 만들기 위해 최소한 두 단계로 나뉩니다.

  1. 사용자가 버튼을 클릭하면 조작 1을 실행하십시오.
  2. 사용자가 컴퓨터를 클릭하면 작업 2 (계속 / 완료 작업 1)를 실행합니다. Grid

최소한 두 가지 제약 조건이 있습니다.

  1. 선택 사항 : API 클라이언트가 반복하기 전에 시퀀스를 완료해야합니다. 작업 2가 완료되면 시퀀스가 ​​완료됩니다.
  2. 작업 1은 항상 작업 2 전에 실행됩니다. 작업 1은 시퀀스를 시작합니다.
  3. API 클라이언트가 조작 2를 실행하기 전에 조작 1을 완료해야합니다.

비 차단 상호 작용을 허용하려면 API 클라이언트에게 두 가지 알림이 필요합니다.

  1. 작업 1 완료 (또는 상호 작용 필요)
  2. 작업 2 (또는 목표) 완료

두 개의 공개 메소드와 두 개의 공개 이벤트를 노출하여 API가이 동작 및 제한 조건을 구현하도록해야합니다.

유틸리티 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백그라운드 스레드에서 실행되는 핸들러에서 유사한 UI 요소에 액세스 하려면 Dispatcher사용 중 Dispatcher.Invoke또는 Dispatcher.InvokeAsync교차 스레드 예외를 피하기 위해 중요한 조작을 큐에 넣어야합니다 .


몇 가지 생각-귀하의 의견에 답변

콘솔 응용 프로그램의 예를 들어서 "더 나은"차단 솔루션을 찾기 위해 나에게 다가 가고 있었기 때문에 나는 당신의 인식이나 관점이 완전히 잘못되었다고 확신했습니다.

"이 두 줄의 코드로 콘솔 응용 프로그램을 고려하십시오.

var str = Console.ReadLine(); 
Console.WriteLine(str);

디버그 모드에서 응용 프로그램을 실행하면 어떻게됩니까? 첫 번째 코드 줄에서 멈추고 콘솔 UI에 값을 입력 한 다음 무언가를 입력하고 Enter 키를 누르면 다음 줄을 실행하고 실제로 입력 한 내용을 인쇄합니다. WPF 응용 프로그램에서 정확히 같은 동작을 생각하고있었습니다. "

콘솔 응용 프로그램은 완전히 다릅니다. 스레딩 개념이 다릅니다. 콘솔 응용 프로그램에는 GUI가 없습니다. 입력 / 출력 / 오류 스트림 만 있습니다. 콘솔 응용 프로그램의 아키텍처를 풍부한 GUI 응용 프로그램과 비교할 수 없습니다. 작동하지 않습니다. 당신은 정말로 이것을 이해하고 받아 들여야합니다.

WPF는 렌더링 스레드와 UI 스레드를 중심으로 빌드됩니다. 이러한 스레드는 사용자 입력 처리와 같이 OS와 통신하기 위해 항상 회전하여 응용 프로그램의 응답 성을 유지합니다 . 이 스레드는 프레임 워크가 필수적인 백그라운드 작업 (마우스 이벤트에 응답하는 것과 같은 것)을 멈추게하므로 일시 정지 / 차단하고 싶지 않습니다.

대기 = 스레드 차단 = 응답하지 않음 = 불량 UX = 성가신 사용자 / 고객 = 사무실 문제.

때로는 응용 프로그램 흐름에서 입력 또는 루틴이 완료되기를 기다려야합니다. 그러나 우리는 메인 스레드를 차단하고 싶지 않습니다.
그렇기 때문에 사람들은 복잡한 비동기 프로그래밍 모델을 개발하여 메인 스레드를 차단하지 않고 개발자가 복잡하고 잘못된 멀티 스레딩 코드를 작성하지 않고도 기다릴 수 있습니다.

모든 최신 응용 프로그램 프레임 워크는 간단하고 효율적인 코드를 개발할 수 있도록 비동기 작업 또는 비동기 프로그래밍 모델을 제공합니다.

비동기 프로그래밍 모델에 저항하기 위해 노력하고 있다는 사실은 저에게 이해력이 부족하다는 것을 보여줍니다. 모든 현대 개발자는 동기식 API보다 비동기식 API를 선호합니다. 심각한 개발자는 await키워드 를 사용 하거나 자신의 방법을 선언 하지 않습니다 async. 아무도 비동기 API에 대해 불평하고 사용하기 불편한 사람이 처음으로 만났습니다.

UI 관련 문제를 해결하거나 UI 관련 작업을보다 쉽게 ​​수행 할 수있는 프레임 워크를 확인하려면 비동기식으로 기대할 수 있습니다.
비동기 적이 지 않은 UI 관련 API는 프로그래밍 스타일을 복잡하게하므로 오류가 발생하기 쉽고 코드를 유지하기가 어렵 기 때문에 낭비입니다.

다른 관점 : 대기가 UI 스레드를 차단한다는 사실을 인정하면 대기가 끝날 때까지 UI가 정지되므로 매우 나쁘고 바람직하지 않은 사용자 경험이 생길 수 있습니다. 이제 깨달았습니다. 개발자가 정확히 이것을 수행-대기 구현?
타사 플러그인의 기능과 루틴이 완료 될 때까지 걸리는 시간을 모릅니다. 이것은 단순히 나쁜 API 디자인입니다. API가 UI 스레드에서 작동하는 경우 API 호출자는 비 블로킹 호출을 수행 할 수 있어야합니다.

내 예에서 볼 수 있듯이 이벤트 중심 접근 방식을 사용하는 것보다 저렴하거나 우아한 솔루션을 거부하는 경우.
원하는 작업을 수행합니다. 루틴 시작-사용자 입력 대기-실행 계속-목표 달성.

대기 / 차단이 왜 나쁜 응용 프로그램 디자인인지 설명하기 위해 여러 번 시도했습니다. 다시, 콘솔 UI를 리치 그래픽 UI와 비교할 수 없습니다. 예를 들어 입력 처리만으로 입력 스트림을 듣는 것보다 훨씬 더 복잡합니다. 나는 당신의 경험 수준과 시작 위치를 모르지만 비동기 프로그래밍 모델을 수용해야합니다. 나는 당신이 그것을 피하려고하는 이유를 모른다. 그러나 전혀 현명하지 않습니다.

오늘날 비동기 프로그래밍 모델은 모든 플랫폼, 컴파일러, 모든 환경, 브라우저, 서버, 데스크톱, 데이터베이스 등 모든 곳에서 구현됩니다. 이벤트 중심 모델은 동일한 목표를 달성 할 수 있지만 백그라운드 스레드에 의존하여 사용하기 (이벤트 구독 / 구독 취소)가 덜 편리합니다. 이벤트 중심은 구식이며 비동기 라이브러리를 사용할 수 없거나 적용 할 수없는 경우에만 사용해야합니다.

"Autodesk Revit에서 정확한 동작을 보았습니다."

행동 (당신이 경험하거나 관찰하는 것)은이 경험이 어떻게 구현되는지와는 많이 다릅니다. 다른 두 가지. Autodesk는 비동기 라이브러리 또는 언어 기능 또는 다른 스레딩 메커니즘을 사용하고있을 가능성이 큽니다. 또한 상황과 관련이 있습니다. 마음에 드는 메소드가 백그라운드 스레드에서 실행될 때 개발자는이 스레드를 차단하도록 선택할 수 있습니다. 그는 이것을해야 할 아주 좋은 이유가 있거나 디자인을 잘못 선택했습니다. 당신은 완전히 잘못된 길을 가고 있습니다;) 차단은 좋지 않습니다.
(Autodesk 소스 코드는 오픈 소스입니까, 어떻게 구현되는지 어떻게 알 수 있습니까?)

난 당신을 화나게하고 싶지 않아요, 믿어주세요. 그러나 API 비동기를 구현하도록 다시 고려하십시오. 개발자가 비동기 / 대기를 사용하지 않는 것은 당신의 머리에만 있습니다. 당신은 분명히 잘못된 사고 방식을 가지고 있습니다. 콘솔 응용 프로그램 인수는 잊어 버려요.

UI 관련 API는 가능할 때마다 반드시 async / await를 사용해야합니다. 그렇지 않으면 API의 클라이언트에 비 차단 코드를 작성하기위한 모든 작업을 남겨 둡니다. API에 대한 모든 호출을 백그라운드 스레드로 랩핑해야합니다. 또는 덜 편안한 이벤트 처리를 사용하십시오. 저를 믿으십시오-모든 개발자 async는 이벤트 처리보다 멤버를 꾸미십시오 . 이벤트를 사용할 때마다 잠재적 인 메모리 누수 위험이 있습니다 (일부 상황에 따라 다름). 그러나 부주의하게 프로그래밍 할 때 위험은 거의 없으며 드물지 않습니다.

차단이 왜 나쁜지 이해하기를 바랍니다. 현대 비동기 API를 작성하기 위해 async / await를 사용하기로 결정했으면합니다. 그럼에도 불구하고 이벤트를 사용하여 비 블로킹을 기다리는 가장 일반적인 방법을 보여 주었지만 async / await를 사용하는 것이 좋습니다.

"API를 사용하면 프로그래머가 UI 등에 액세스 할 수 있습니다. 이제 프로그래머가 단추를 클릭 할 때 최종 사용자에게 UI에서 포인트를 선택하도록 추가 기능을 개발하려고합니다."

플러그인이 UI 요소에 직접 액세스하지 못하게하려면 추상 객체를 통해 이벤트를 위임하거나 내부 구성 요소를 노출하는 인터페이스를 제공해야합니다.
API는 내부적으로 애드 인 대신 UI 이벤트를 구독 한 다음 해당 "래퍼"이벤트를 API 클라이언트에 노출시켜 이벤트를 위임합니다. API는 특정 애플리케이션 구성 요소에 액세스하기 위해 추가 기능을 연결할 수있는 후크를 제공해야합니다. 플러그인 API는 외부에서 내부에 액세스 할 수 있도록 어댑터 또는 파사드처럼 작동합니다.
어느 정도의 격리를 허용합니다.

Visual Studio에서 플러그인을 관리하거나 구현할 수있는 방법을 살펴보십시오. Visual Studio 용 플러그인을 작성하고이 작업을 수행하는 방법에 대한 조사를 수행하려고합니다. Visual Studio는 인터페이스 또는 API를 통해 내부를 노출한다는 것을 알게 될 것입니다. EG 당신은 코드 에디터를 조작하거나 에디터의 실제 접근 없이 에디터의 컨텐츠에 대한 정보를 얻을 수 있습니다.


안녕하세요, 다른 관점에서 질문에 접근 해 주셔서 감사합니다. 질문이 세부 사항에 약간 모호한 경우 죄송합니다. 이 두 줄의 코드가 포함 된 콘솔 응용 프로그램을 고려하십시오. var str = Console.ReadLine(); Console.WriteLine(str);디버그 모드에서 응용 프로그램을 실행하면 어떻게됩니까? 첫 번째 코드 줄에서 멈추고 콘솔 UI에 값을 입력 한 다음 무언가를 입력하고 Enter 키를 누르면 다음 줄이 실행되고 실제로 입력 한 내용이 인쇄됩니다. WPF 응용 프로그램에서 정확히 동일한 동작을 생각하고있었습니다.
Vahid

내가 개발중인 CAD 응용 프로그램에서 사용자는 addins / plugins로 확장 할 수 있어야합니다. API를 사용하면 프로그래머가 UI 등에 액세스 할 수 있습니다. 이제 프로그래머가 버튼을 클릭 할 때 최종 사용자에게 UI에서 점을 선택하도록 요청한 다음 코드가 다른 작업을 수행하는 추가 기능을 개발하려고한다고 가정하십시오. 주어진 포인트로 멋진 물건. 어쩌면 그들은 다른 점을 골라서 선을 그릴 것입니다.
Vahid

Autodesk Revit에서 정확한 동작을 보았습니다.
Vahid

1
귀하의 요구 사항에 대해 할 말이 있습니다. 업데이트 된 답변을 읽으십시오. 어떻게 든 더 길어지기 때문에 답장을 게시했습니다. 나는 당신이 정말로 나를 촉발했다는 것을 인정합니다. 읽는 동안, 나는 당신을 화나게하고 싶지 않다는 것을 명심하십시오.
BionicCode

업데이트 된 답변에 감사드립니다. 물론 기분 나쁘지 않습니다. 반대로, 나는 당신이 이것에 시간과 노력을 주셔서 정말 감사합니다.
Vahid

5

나는 개인적으로 이것이 모든 사람들에 의해 지나치게 복잡해 졌다고 생각하지만, 이것이 특정 방식으로 수행되어야하는 이유를 완전히 이해하지 못하고 있지만 간단한 부울 검사가 여기에서 사용될 수있는 것처럼 보입니다.

무엇보다도, 당신의 그리드 히트 검증 설정에 따라 확인 BackgroundIsHitTestVisible속성을, 그렇지 않으면하지 않습니다 심지어 캡처 마우스 클릭.

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

그런 다음 "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
}

안녕하세요 트론 드, 당신이 그 질문을 오해했다고 생각합니다. 내가 필요한 것은 코드가 Utility.PickPoint (View)에서 중지하고 사용자가 Grid를 클릭 한 후에 만 ​​계속되는 것입니다.
Vahid

오, 나는 완전히 오해했다. 사과합니다. 실제로 멈추기 위해 모든 것이 필요하다는 것을 깨닫지 못했습니다. 전체 UI가 차단되므로 멀티 스레딩이 없으면 가능하지 않다고 생각합니다.
Tronald

그것이 가능하지 않은지 여전히 확실하지 않습니다. 멀티 스레드 솔루션이 아닌 async / await를 사용하면 가능합니다. 그러나 내가 필요한 것은 async / await 솔루션의 대안입니다.
Vahid

1
물론, 당신은 async / await를 사용할 수 없다고 언급했습니다. 디스패처와 주 스레드와 분리 된 스레드 (UI에서 실행되는 스레드)를 사용해야하는 것처럼 보입니다. 내가 관심을 가지면서 다른 길을 찾길 바랍니다
Tronald April

2

몇 가지를 시도했지만 없이는 할 수 없습니다 async/await. 우리가 그것을 사용하지 않으면 원인이 DeadLock되거나 UI가 차단 된 다음 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

    }
}

@thanks, 이것은 async / await를 사용하는 다른 질문과 동일한 대답입니다.
Vahid

아, 네 ! 나는 지금 그것을 알아 차 렸지만 그것이 내가 찾은 유일한 방법이라고 생각합니다
Rao Hammas Hussain

2

다음을 사용하여 비동기 적으로 차단할 수 있습니다 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으로 차단할 수 없으며,을 클릭하여 처리 할 수 ​​없기 때문에 ( 예 : 동시에 차단 및 이벤트를 처리 할 수 ​​없음) 할 수 없습니다.


다른 답변을 주셔서 감사합니다. 예를 들어 Console.Readline ()에서 어떻게 수행되는지 궁금합니다. 디버거 에서이 메소드에 도달하면 무언가를 입력하지 않으면 마술처럼 중지됩니다. 콘솔 응용 프로그램에서 근본적으로 다른가요? WinForms / WPF 애플리케이션에서 동일한 동작을 수행 할 수 없습니까? Autodesk Revit의 API에서 이것을 보았습니다. PickPoint () 메소드가있어 화면에서 점을 선택해야하며 비동기 / 대기가 사용되는 것을 보지 못했습니다! 적어도 await 키워드를 숨기고 sync 메소드에서 어떻게 든 호출 할 수 있습니까?
Vahid

@Vahid : Console.Readline blocks , 즉 행을 읽을 때까지 반환되지 않습니다. 당신의 PickPoint방법은하지 않습니다. 즉시 돌아옵니다. 잠재적으로 차단 될 수는 있지만 그 동안 내 답변에 쓴 것처럼 UI 입력을 처리 할 수 ​​없습니다. 즉, 동일한 동작을 얻으려면 메서드 내부의 클릭을 처리해야합니다.
mm8

Console.Realine ()은 차단되지만 동시에 KeyPress 이벤트는 허용됩니다. 여기서 똑같은 동작을 할 수 없습니까? PickPoint ()로 차단하고 MouseEvent 만 허용합니까? 콘솔에서는 가능하지만 UI 기반 응용 프로그램에서는 불가능한 이유를 파악할 수 없습니다.
Vahid

그런 다음 PickPoint마우스 이벤트를 처리 하는 별도의 디스패처를 설정해야 합니다. 나는 당신이 어디로 가고 있는지 보지 못합니까?
mm8

1
@Vahind : 코드를 비 동기화하고 사용자가 메소드를 기다릴 수 있습니까? 이것이 UI 개발자로서 기대할 수있는 API입니다. UI 애플리케이션에서 차단 메소드를 호출하는 것은 의미가 없습니다.
mm8

2

기술적으로 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...
}

단점 : 샘플 코드처럼 버튼 이벤트 핸들러에서이 메소드를 직접 호출하면 교착 상태가 발생하고 애플리케이션이 응답하지 않는 것을 볼 수 있습니다. 사용자의 클릭을 기다리는 데 유일한 UI 스레드를 사용하고 있기 때문에 그리드에서 사용자의 클릭을 포함하여 사용자의 작업에 응답 할 수 없습니다.

메소드 소비자는 교착 상태를 방지하기 위해 다른 스레드에서 메소드를 호출해야합니다. 그것이 보장 될 수 있다면 괜찮습니다. 그렇지 않으면 다음과 같이 메소드를 호출해야합니다.

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발명 된 이유 입니다.


Ken에게 감사합니다. addin이 다른 스레드에서 시작한 다음 해당 이벤트가 기본 UI 스레드를 차단하지 않을 수 있습니까?
Vahid

@Vahid 예와 아니오. 예, 다른 스레드에서 차단 메소드를 호출하여 다른 메소드로 랩핑 할 수 있습니다. 그러나 UI 차단을 피하려면 UI 스레드 이외의 다른 스레드에서 래퍼 메서드를 계속 호출해야합니다. 래퍼가 호출 스레드동 기적 이면 호출 스레드 를 차단하기 때문입니다 . 내부적으로 래퍼가 다른 스레드를 차단하더라도 결과를 기다려야하며 스레드 호출을 차단해야합니다. 호출자가 UI 스레드에서 랩퍼 메소드를 호출하면 UI가 차단됩니다.
켄흥

0

디자인 자체에 문제가 있다고 생각합니다. API가 특정 요소에서 작동하는 경우 다른 요소가 아닌이 요소의 이벤트 핸들러에서 사용해야합니다.

예를 들어, 여기서 그리드에서 클릭 이벤트의 위치를 ​​가져 오려면 단추 요소가 아니라 그리드 요소의 이벤트와 연관된 이벤트 핸들러에서 API를 사용해야합니다.

이제 버튼을 클릭 한 후에 만 ​​그리드의 클릭을 처리해야하는 경우 버튼 책임은 그리드에 이벤트 핸들러를 추가하는 것이며 그리드의 클릭 이벤트는 메시지 상자를 표시하고 제거합니다. 이 이벤트 핸들러는 버튼으로 추가되어 클릭 후 더 이상 트리거되지 않습니다 ... (UI 스레드를 차단할 필요가 없습니다)

버튼 클릭시 UI 스레드를 차단하면 UI 스레드가 그리드에서 클릭 이벤트를 트리거 할 수 없다고 생각합니다.


0

우선 UI 스레드는 초기 질문에서 얻은 답변처럼 차단 할 수 없습니다.
당신이 그것에 동의 할 수 있다면, 고객이 덜 수정하게하기 위해 비동기 / 대기를 피하는 것이 가능하며 심지어 멀티 스레딩이 필요하지 않습니다.

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;
    }
}   

그러나 UI 스레드 또는 "코드 흐름"을 차단하려는 경우 불가능하다는 대답이 있습니다. UI 스레드가 차단되면 더 이상 입력을받을 수 없습니다.
콘솔 응용 프로그램에 대해 언급 했으므로 간단한 설명 만합니다.
콘솔 응용 프로그램을 실행하거나 AllocConsole콘솔 (창)에 연결되지 않은 프로세스에서 호출 하면 콘솔 (창)을 제공 할 수있는 conhost.exe가 실행되고 콘솔 응용 프로그램 또는 호출자 프로세스가 콘솔에 연결됩니다 ( 창문).
따라서 Console.ReadKey콘솔 창의 UI 스레드를 차단하지 않는 호출자 스레드를 차단 할 수있는 코드 는 콘솔 응용 프로그램이 입력을 대기하지만 마우스 클릭과 같은 다른 입력에 응답 할 수있는 이유입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.