C#でイベントが発生するまでコードフローをブロックする方法


9

ここにGridとがありButtonます。ユーザーがボタンをクリックすると、Utilityクラスのメソッドが実行され、アプリケーションがグリッドをクリックするように強制します。コードフローはここで停止し、ユーザーがをクリックするまで続行しないでくださいGrid

私はここまでに同様の質問をしました:

ユーザーがC#WPFをクリックするまで待ちます

その質問では、動作するasync / awaitを使用して回答を得ましたが、これをAPIの一部として使用するので、コンシューマーは自分のメソッドを非同期は必要ありません。

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を実行してその操作STATEを保存する方法です。ユーザーがグリッドをクリックする必要があります。
ラオハマスフセイン

@RaoHammasHussain質問に役立つリンクを追加しました。ユーティリティメソッドはAPIの一部であり、APIのユーザーがエンドユーザーに画面のクリックを要求するたびに呼び出されます。通常のWindowsアプリケーションまたはConsole.Readline()メソッドのテキストのプロンプトウィンドウのように考えてください。これらの場合、ユーザーが何かを入力するまでコードフローは停止します。正確なものが欲しいのですが、今度はユーザーが画面をクリックします。
Vahid

AutoResetEventあなたが望むものではありませんか?
ラオハマスフセイン

@RaoHammasHussain私はそう思うが、実際にここでそれを使用する方法がわかりません。
Vahid

意図的にWAIT STATEを実装しているようなものです。それは本当に必要ですか?これvar point = Utility.PickPoint(Grid grid);をグリッドクリックメソッドに入れることはできませんか?何らかの操作を行って応答を返しますか?
ラオハマスフセイン

回答:


7

「イベントが発生するまでコードフローをブロックする方法」

あなたのアプローチは間違っています。イベント駆動型は、イベントをブロックして待機することを意味しません。あなたは決して待つことはありません、少なくともあなたは常にそれを避けるために一生懸命努力します。待機はリソースを浪費し、スレッドをブロックし、おそらくデッドロックまたはゾンビスレッド(シグナルが発生しない場合)のリスクをもたらします。
スレッドがイベントを待機するのをブロックすることは、イベントの概念と矛盾するため、アンチパターンであることは明らかです。

通常、2つの(モダン)オプションがあります。非同期APIまたはイベント駆動型APIを実装します。APIを非同期で実装したくないので、イベント駆動型APIが残ります。

イベント駆動型APIの重要な点は、呼び出し側に結果を同期的に待機させたり、結果をポーリングさせたりする代わりに、呼び出し側に続行させ、結果が準備できたら、または操作が完了したら通知を送信することです。その間、呼び出し元は他の操作を実行し続けることができます。

スレッディングの観点から問題を見ると、イベント駆動型APIにより、ボタンのイベントハンドラーを実行するUIスレッドなどの呼び出しスレッドが、UI要素のレンダリングや処理など、UI関連の操作を自由に継続して処理できるようになります。マウスの動きやキーの押下などのユーザー入力。非同期APIと同じ効果がありますが、便利ではありません。

実際に実行しようとしていること、Utility.PickPoint()実際に実行していること、タスクの結果について、またはユーザーが「グリッド」をクリックしなければならない理由について十分な詳細情報を提供しなかったため、これ以上の解決策は提供できません。私はあなたの要件を実装する方法の一般的なパターンを提供できます。

フローまたは目標は、それを一連の操作にするために、少なくとも2つのステップに明らかに分割されます。

  1. ユーザーがボタンをクリックしたときに操作1を実行する
  2. ユーザーがをクリックすると、操作2(操作1の続行/完了)を実行します。 Grid

少なくとも2つの制約あり:

  1. オプション:APIクライアントがシーケンスを繰り返すことができるようになる前に、シーケンスを完了する必要があります。操作2が完了すると、シーケンスが完了します。
  2. オペレーション1は常にオペレーション2の前に実行されます。オペレーション1はシーケンスを開始します。
  3. APIクライアントが操作2の実行を許可される前に、操作1を完了する必要があります

これには、非ブロッキング相互作用を許可するために、APIのクライアントに2つの通知が必要です。

  1. 操作1が完了(または操作が必要)
  2. 操作2(または目標)が完了しました

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.InvokeDispatcher.InvokeAsyncスレッド間の例外を回避する必要があります。


いくつかの考え-コメントへの返信

あなたが「より良い」ブロッキングソリューションを見つけるために私に近づいていたので、コンソールアプリケーションの例を考えると、私はあなたの見方や見方がまったく間違っているとあなたに納得させました。

「この2行のコードを含むコンソールアプリケーションを検討してください。

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の呼び出し元はAPIへの非ブロッキング呼び出しを実行できる必要があります。

私の例に示すように、イベント駆動型のアプローチを使用するよりも、安価で優雅な唯一のソリューションを否定する場合。
それはあなたが望むことを行います:ルーチンを開始します-ユーザー入力を待ちます-実行を続けます-目標を達成します。

待機/ブロックが悪いアプリケーション設計である理由を説明するために、私は実際に何度か試みました。繰り返しますが、コンソールUIをリッチなグラフィカルUIと比較することはできません。たとえば、入力ストリームだけを聞くよりも入力処理だけの方がはるかに複雑です。あなたの経験レベルとどこから始めたのか本当にわかりませんが、非同期プログラミングモデルを採用する必要があります。あなたがそれを避けようとする理由がわかりません。しかし、それはまったく賢明ではありません。

今日、非同期プログラミングモデルは、あらゆるプラットフォーム、コンパイラ、あらゆる環境、ブラウザ、サーバー、デスクトップ、データベースのあらゆる場所に実装されています。イベント駆動型モデルでも同じ目標を達成できますが、バックグラウンドスレッドに依存している(イベントへのサブスクライブ/サブスクライブ解除)の使用はあまり便利ではありません。イベント駆動型は旧式であり、非同期ライブラリーが利用できないか適用できない場合にのみ使用してください。

「Autodesk Revitで正確な動作を見てきました。」

動作(ユーザーが体験または観察するもの)は、この体験が実装される方法とは大きく異なります。2つの異なるもの。オートデスクは、非同期ライブラリ、言語機能、またはその他のスレッドメカニズムを使用している可能性が非常に高いです。また、コンテキストにも関連しています。気になるメソッドがバックグラウンドスレッドで実行されている場合、開発者はこのスレッドをブロックすることを選択できます。彼には、これを行う十分な理由があるか、設計を誤って選択したかのどちらかです。あなたは完全に間違った軌道に乗っています;)ブロッキングは良くありません。
(オートデスクのソースコードはオープンソースですか?それとも、どのように実装されているかをどうやって知るのですか?)

私はあなたを怒らせたくありません、私を信じてください。ただし、APIを非同期で実装することを再検討してください。開発者がasync / awaitを使いたくないのはあなたの頭の中だけです。あなたは明らかに間違った考え方を持っていました。そして、そのコンソールアプリケーションの引数を忘れてください-それはナンセンスです;)

UI関連のAPI は、 可能な場合は常に非同期/待機を使用する必要があります。それ以外の場合は、ノンブロッキングコードを作成するすべての作業をAPIのクライアントに任せます。APIへのすべての呼び出しをバックグラウンドスレッドにラップするように強制します。または、あまり快適でないイベント処理を使用します。私を信じて-すべての開発者はasync、イベント処理を行うよりも、むしろでメンバーを飾ります。イベントを使用するたびに、潜在的なメモリリークのリスクが発生する可能性があります-状況によって異なりますが、リスクは現実的であり、不注意にプログラミングした場合はまれではありません。

ブロッキングが悪い理由を理解していただければ幸いです。最新の非同期APIを作成するためにasync / awaitを使用することを決定したいと思います。それでも、イベントを使用して非ブロッキングを待機する非常に一般的な方法を示しましたが、非同期/待機を使用することをお勧めします。

「APIにより、プログラマーはUIなどにアクセスできるようになります。プログラマーがボタンをクリックすると、最終ユーザーがUIでポイントを選択するように要求されるアドインを開発したいとします。」

プラグインがUI要素に直接アクセスできないようにする場合は、イベントを委任するためのインターフェースを提供するか、抽象化されたオブジェクトを介して内部コンポーネントを公開する必要があります。
APIは、アドインに代わってUIイベントを内部的にサブスクライブし、対応する「ラッパー」イベントをAPIクライアントに公開することにより、イベントを委任します。APIは、特定のアプリケーションコンポーネントにアクセスするためにアドインが接続できるフックを提供する必要があります。プラグインAPIは、外部から内部へのアクセスを提供するためのアダプターまたはファサードのように機能します。
ある程度の分離を可能にするため。

Visual Studioがプラグインを管理する方法またはプラグインを実装できるようにする方法を見てください。Visual Studio用のプラグインを作成して、これを行う方法についていくつかの調査を行いたいとしましょう。Visual StudioがインターフェイスまたはAPIを介して内部を公開していることに気付くでしょう。EGあなたは、コードエディタを操作したりすることなく、エディタのコンテンツについての情報を得ることができ、実際のそれへのアクセス。


こんにちは、別の観点から質問にアプローチしていただきありがとうございます。質問が詳細で少しあいまいな場合は申し訳ありません。これらの2行のコードを含むコンソールアプリケーションを考えてみます。var str = Console.ReadLine(); Console.WriteLine(str);アプリケーションをデバッグモードで実行するとどうなりますか。コードの最初の行で停止し、コンソールUIで値を入力するように強制します。次に、何かを入力してEnterキーを押すと、次の行が実行され、入力した内容が実際に出力されます。私はWPFアプリケーションでまったく同じ動作について考えていました。
Vahid

私が開発しているCADアプリケーションでは、ユーザーはアドイン/プラグインによってそれを拡張できるはずです。APIを使用すると、プログラマーはUIなどにアクセスできます。ここで、プログラマーがボタンをクリックすると、最終ユーザーがUI内のポイントを選択するように求められ、コードが他のことを行うアドインを開発したいとします。与えられたポイントを持つクールなもの。多分彼らは別のポイントを選んで線を引くなどと尋ねるでしょう
Vahid

Autodesk Revitでの正確な動作を見てきました。
Vahid

1
私はあなたの要件について何か言いたいことがあります。私の更新された答えを読んでください。なんとか長くなってしまったのでそこに返信を投稿しました。あなたが本当に私を引き起こしたことを認めます。読んでいる間、私はあなたを怒らせたくないということを覚えておいてください。
BionicCode

更新された回答をありがとうございます。もちろん、私は気分を害していません。それどころか、あなたがこれに時間と労力を費やしてくれたことに本当に感謝しています。
Vahid

5

個人的にはこれは複雑すぎると思いますが、なぜこれを特定の方法で行う必要があるのか​​完全には理解していませんが、ここでは単純なブールチェックを使用できるようです。

何よりもまず、BackgroundおよびIsHitTestVisibleプロパティを設定してグリッドをヒットテスト可能にします。そうしないと、マウスクリックもキャプチャされません。

<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)で停止し、ユーザーがグリッドをクリックした後にのみ続行することです。
Vahid

ああ、私は完全に誤解しました。申し訳ありませんが、実際に停止するためにすべてが必要だとは思いませんでした。UI全体がブロックされるので、マルチスレッドなしではそれが可能ではないと思います。
トロナルド

それが可能かどうか、私にはまだわかりません。マルチスレッドのソリューションではないasync / awaitを使用することは確実に可能です。しかし、私が必要なのは、非同期/待機ソリューションの代替です。
Vahid

1
もちろん、非同期/待機は使用できないとおっしゃっていました。ディスパッチャとメインスレッド(UIで実行される)から分離されたスレッドを使用する必要があるようです。私自身が興味を持っているので、別の方法を見つけてほしい
トロナルド

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

    }
}

@おかげで、これは非同期/待機を使用する他の質問に対して私が得た同じ回答です。
Vahid

ああそう!今気付いたのですが、それが機能している唯一の方法だと思います
ラオハマスフセイン

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 ブロック、つまり、行が読み取られるまで戻りません。あなたのPickPoint方法はしません。すぐに戻ります。ブロックする可能性がありますが、その間、私が私の回答に書いたように、UI入力を処理できなくなります。つまり、同じ動作を実現するには、メソッド内のクリックを処理する必要があります。
mm8

Console.Realine()はブロックしますが、同時にKeyPressイベントは許可されます。ここでまったく同じ動作をすることはできませんか?PickPoint()でブロックし、MouseEventsのみを許可しますか?コンソールでは可能であるが、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を除いて、APIのコンシューマーに問題を引き起こす可能性があります。それasync/awaitが発明された理由です。


ケンさん、アドインが別のスレッドから始まり、そのイベントがメイン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.