質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.36%

  • C#

    8245questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

async/awaitでイベント待ちってできるのでしょうか?

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 917

wwbQzhMkhhgEmhU

score 156

前提・実現したいこと

async/awaitなど最新?C#の技術を使ってみたいのですが、イベントを待って戻り値を返すI/Fのある部分をasync/awaitで表現できませんでした。
いろいろ考えた結果、自分では下のソースのような、Monitorを使った同期機構でしか実現できなかったのですが、もっといい方法があれば教えてください。

下のソースではMyReader.Read()が該当箇所です。Readerインターフェースを実装しており、ただただ、同期I/Oで読んだ結果を返すI/Fです。
このI/Fを実装し、外からイベントで渡されてくるデータを返したいのです。同期I/Oなのでブロックする必要があり、要求されたデータが来るまで待つ必要があります。ここをなんとかawaitで実現したいのですが、いい方法を思いつきませんでした。

なお、EventHolder/Readerの形はいじれないので、ご注意ください。
また主旨に関係ないご指摘も、気づいたことは何でも言って頂けると嬉しいです。

該当のソースコード

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EventAsyncAwait
{
    interface Reader
    {
        int Read();
    }

    class MyEventArgs
    {
        public int val { get; set; }
    }

    class MyReader : Reader
    {
        List<int> buffer = new List<int>();

        public int Read()
        {
            int result = 0;
            lock (buffer)
            {
                while (buffer.Count == 0)
                    Monitor.Wait(buffer);
                result = buffer[0];
                buffer.RemoveAt(0);
            }
            return result;
        }

        public MyReader(EventHolder parent)
        {
            parent.Event += (object sender, MyEventArgs e) =>
            {
                lock (buffer)
                {
                    buffer.Add(e.val);
                    Monitor.PulseAll(buffer);
                }
            };
        }
    }

    class EventHolder
    {
        public event EventHandler<MyEventArgs> Event;

        public void Fire(int v)
        {
            Event(this, new MyEventArgs() { val = v });
        }
    }

    class Program
    {
        static EventHolder holder = new EventHolder();

        static void Main(string[] args)
        {
            MyReader reader = new MyReader(holder);
            Task.Run(async () =>
            {
                await Task.Delay(3000);
                holder.Fire(5);
            });
            Console.WriteLine(reader.Read());
        }
    }
}

※質問しておいて何ですが、本日反応できるのは夕方くらいからになります。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • wwbQzhMkhhgEmhU

    2019/02/10 14:06

    Task.Run()の戻り値からではなく、Reader.Read()の実装をしたいんですよ。。。

    キャンセル

  • tamoto

    2019/02/10 14:45

    要件の確認をさせてください。
    このイベントは「1回きり」発動することを想定したものですか?それとも、複数回発動する、「ストリーム」を想定していますか?

    キャンセル

  • wwbQzhMkhhgEmhU

    2019/02/10 14:55

    最終的には複数回だけど、1回きりの方法すら思いついていないので、まずは1回きりでも大丈夫、という意味で、ここでは1回しか待っていません。

    キャンセル

回答 4

+3

他にも方法はあるでしょうが、IObservable<T> を使ってください。

Reactive Extensions を使うと簡単です。これを使うには NuGet で System.Reactive をインストールする必要があります。

追記

例えば Reactive Extensions を使ってボタンのクリックイベントを受け取るには次のようにします。

using System;
using System.Reactive.Linq;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Observable
                .FromEventPattern<EventArgs>(button1, nameof(Button.Click))
                .Subscribe(_ => MessageBox.Show("クリックされた!"));
        }
    }
}

追記

ボタンを二つ貼り付け、button2 のイベントハンドラを次のように書き換えてください。
そして button2 を押した後、button1 を押すとメッセージが表示されます。

private async void button2_Click(object sender, EventArgs e)
{
    var task = Observable
        .FromEventPattern<EventArgs>(button1, nameof(Button.Click))
        .Take(1)
        .ToTask();
    await task;
    MessageBox.Show("button1 が押されました");
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/10 13:24

    すみませんが、Observer/Observableパターンでは解決しないと思います。
    そもそもイベントハンドラもコールバックも、Observer/Observableパターンの一種です。

    キャンセル

  • 2019/02/10 13:27

    一種ならできるのでは?
    困っていることは何ですか?
    どうしても Task を使いたいということですか?

    キャンセル

  • 2019/02/10 13:32

    はい、どうしてもTaskを使いたいと考えてください。
    古くからある同期機構によるイベント待ちをしていますが、Taskや、それを微妙に進ませた?async/awaitに置き換えられるのか、という課題なので。

    キャンセル

  • 2019/02/10 13:35

    困っていることは、同期メソッドから非同期メソッドを呼びたい、その際に必ず Task を使い、Task が終わるまでブロックしたいということで間違いありませんか?

    キャンセル

  • 2019/02/10 13:35

    すべて非同期に置き換えることはできないという前提ですね?

    キャンセル

  • 2019/02/10 13:41

    ちなみに、コンソールアプリから非同期を呼び出す際に、この Main は間違っているということも指摘されていますが、これはご存知の上で放置しているのですか?
    コンソールが関係ないというのはどういうことなのか、説明してください。

    キャンセル

  • 2019/02/10 13:41

    同期メソッドの戻り値を返すまで、非同期イベントを待つ方法を考えたいのです。
    質問に書いたとおり、ReaderとEventHolderを変えずに、同じ動きを実装したいということです。

    キャンセル

  • 2019/02/10 13:42

    質問と食い違います。
    非同期メソッドから同期メソッドを呼び出すことについては何の問題もありません。
    ただ普通に呼ぶだけです。

    キャンセル

  • 2019/02/10 13:42

    ん?間違っている認識はなかったです。すみません、どういうことか教えて頂けませんか?

    キャンセル

  • 2019/02/10 13:43

    非同期メソッドから同期メソッドを呼び出すとは言っていません。。。

    キャンセル

  • 2019/02/10 13:46

    同期メソッドの戻り値を返すというのは、同期メソッドを呼ぶ必要があります。
    そうすると、その同期メソッドが終わるまで待つ必要があります。
    その後、非同期のイベントが終了するまで待機するということで間違いありませんか?

    キャンセル

  • 2019/02/10 13:51

    イベントは同期でも非同期でもいいんですが、同期メソッドの中で、別のメソッド?が呼ばれるまで戻り値を返すのを待ちたいということです。
    ただ、質問はずっと変えてないんですが、どこがそんなに不明なのでしょう?

    キャンセル

  • 2019/02/10 13:53

    それがどうして IObservable でできないのかわからないからです。
    質問の意図が伝わっていない可能性があるので聞いています。

    キャンセル

  • 2019/02/10 14:01

    ReaderのReadメソッドを実装したいんですよ。どうやったら出来るのか教えてください。

    キャンセル

  • 2019/02/10 14:03 編集

    どう言えば通じるんだろう。

    キャンセル

  • 2019/02/10 14:05

    私の台詞です。。。

    キャンセル

  • 2019/02/10 14:06

    わからないことがあるので、質問をしています。
    答えがないのでわかりません。

    Read メソッドは実装済みで変更できないんでしょう?
    イベントをシーケンシャルに処理したいなら全部非同期にして IObservable で処理するのが簡単です。
    同期メソッドを止めながらブロックしないという意味がよくわかりません。
    止めるのとブロックするのは同義です。

    キャンセル

  • 2019/02/10 14:07

    async/await は非同期で使うものです。
    これはよろしいですか?

    キャンセル

  • 2019/02/10 14:08

    止めたいだけなら Task.Wait が使えますが、これも良いんですよね?

    キャンセル

  • 2019/02/10 14:10

    > Read メソッドは実装済みで変更できないんでしょう?
    はい、このI/Fは変更できません。始めからそう書いています。
    > async/await は非同期で使うものです。
    > これはよろしいですか?
    別にどこで使ってもいいと思いますが、awaitを使うなら、asyncでないとダメですね。

    キャンセル

  • 2019/02/10 14:11

    > 止めたいだけなら Task.Wait が使えますが、これも良いんですよね?
    はい

    キャンセル

  • 2019/02/10 14:13

    どうして肝心なところがいつも無視されるんでしょうか。
    ひとつづつ聞いた方がいいんでしょうかね。

    一つ目。
    全部非同期にすることはできませんか?

    キャンセル

  • 2019/02/10 14:17

    ?には回答しましたし、無視は一度もしてないつもりですよ。
    Readerの実装が出来るのなら。できるんですか?
    あなたの言う同期・非同期の意味合いが正確ではありません。

    キャンセル

  • 2019/02/10 14:18 編集

    同期メソッドを止める場合には必ずメインスレッドがそこで止まります。
    メインスレッドを止めてはいけないのなら、同期メソッドを止めてはいけません。
    つまり、非同期メソッドの戻り値をそこで待ってはいけません。

    この解決策としては、Task を使った非同期メソッドに置き換えるか、メインスレッド以外を待たせるかということになります。

    これが「全部非同期にする」ということです。
    これが受け入れられないということでいいんでしょうか?

    キャンセル

  • 2019/02/10 14:24

    一応引用しておきます。
    > ReaderのReadメソッドを実装したいんですよ。どうやったら出来るのか教えてください。

    これは撤回ということですよね?

    キャンセル

  • 2019/02/10 14:27

    あなたの文章の間違い探しをしているわけではないです。
    私はawait処理をC言語のsetjmp/longjmpのようなものと捉えています。
    そういう意味では、asyncでないという意味での同期メソッドをTask.Wait()するなら、必ずしもメインスレッドが止まるわけでもないです。Waitしてる間、メインスレッドで動作できます。

    キャンセル

  • 2019/02/10 14:28

    また別スレッドでないという仮定もしていません

    キャンセル

  • 2019/02/10 14:29

    > これは撤回ということですよね?
    撤回したことはありませんよ

    キャンセル

  • 2019/02/10 14:29

    では Task.Wait を使えばいいのでは?

    キャンセル

  • 2019/02/10 14:33

    だからどうやればできるの?って聞いています。

    キャンセル

  • 2019/02/10 14:36

    Task.Wait でイベントを待つことができれば解決しますか?
    イベントが来るまでずっと止まりますが、それができれば解決ということでいいですか?

    キャンセル

  • 2019/02/10 14:37

    始めからそう言ってるつもりですけど。。。
    具体的には、NAudioのWasapiLoopbackCaptureというクラスを使って、デスクトップの音を拾っています。
    これをネットワーク越しに送りたいのですが、この人はStreamのI/Fを持っていないので、そこを実装してあげようとしているのです。ただ、WasapiLoopbackCaptureは、イベントでデータを送ってくるので、そこをReadで待ってあげないといけません。そこを可能であればTask/async/awaitで実装したいのです。

    キャンセル

  • 2019/02/10 14:39

    最初から言っていることがわからないので聞いています。
    イエスかノーで答えてください。

    キャンセル

  • 2019/02/10 14:47

    > Task.Wait でイベントを待つことができれば解決しますか?
    イベントで渡される引数をRead側でもらえれば、Task.Wait でイベントを待つことで解決します
    > イベントが来るまでずっと止まりますが、それができれば解決ということでいいですか?
    イベントで渡される引数をもらえれば、止まっても大丈夫です。

    キャンセル

  • 2019/02/10 14:52

    イベントは回答に示したように IObservable<EventArgs> に変換できます。
    そして IObservable は ToTask() で Task に変換できます。
    これを Wait してください。
    ふつうはそこで止まりますが。

    キャンセル

  • 2019/02/10 14:54

    ToTask の戻り値は Task<System.Reactive.EventPattern<EventArgs>> です。

    キャンセル

  • 2019/02/10 14:54

    横からすみません。
    質問者さんへ。「EventHolder/Readerの形はいじれない」とおっしゃっていますが、現状の同期Read(`int Read();`)と、非同期Read(`Task<int> ReadAsync();`)はどうあがいても異なるAPIになってしまうため、その要件を満たしつつ非同期にするのは「不可能」なのです。という話をしています。現状では`Read()`を呼んだときに`int`を返す必要があり、ここで結果を同期待ちすることが必須となるため、非同期化のしようがありません。

    キャンセル

  • 2019/02/10 14:56

    だから、Task.Wait()でいいという話まで、Zuishinさんにしていたところです。。。
    非同期メソッドかどうかというところは重要ではありません。

    キャンセル

  • 2019/02/10 14:58

    ではこれで解決しましたね?
    低評価を取り消して解決済みにしてください。

    キャンセル

  • 2019/02/10 14:59

    はい、Task.Wait()を使うということは、非同期化した上で同期化する、ということになるので、何も非同期になっていないし実行速度は落ちるし何の意味もないのですが、それをやりたいということですか?

    キャンセル

  • 2019/02/10 15:01

    > ではこれで解決しましたね?
    何が解決したんでしょう?ごめんなさい、意味が分からないです。

    キャンセル

  • 2019/02/10 15:02

    え?
    これで Task.Wait できるでしょ?

    キャンセル

  • 2019/02/10 15:02

    > はい、Task.Wait()を使うということは、非同期化した上で同期化する、ということになるので、何も非同期になっていないし実行速度は落ちるし何の意味もないのですが、それをやりたいということですか?
    はい。
    別に非同期I/Oを呼び出して、同期I/Oを実装、は普通に行われると思いますが。。。

    キャンセル

  • 2019/02/10 15:03

    もう一度書きましょうか?

    イベントは回答に示したように IObservable<EventArgs> に変換できます。
    そして IObservable は ToTask() で Task に変換できます。
    これを Wait してください。

    キャンセル

  • 2019/02/10 15:03

    > これで Task.Wait できるでしょ?
    それでイベントを待つ方法を思いつかなかったんですよ。。。

    キャンセル

  • 2019/02/10 15:05

    > イベントは回答に示したように IObservable<EventArgs> に変換できます。
    > そして IObservable は ToTask() で Task に変換できます。

    私の回答を読んでください。
    どこにも難しいことは書いていないと思いますが。

    イベントが Task に変換できればいいのでしょう?

    キャンセル

  • 2019/02/10 15:07

    これ通知する側の機能ではない?こちらはIObserver側ですよ

    キャンセル

  • 2019/02/10 15:08

    非同期I/Oを同期的に呼び出すは普通には行われません……それは非同期を理解していない人が書いた危険で非効率なコードという意味です……

    キャンセル

  • 2019/02/10 15:10

    非同期I/Oをラップする形で同期I/Oを書いたらそうなるんですね。。。
    悲しいです。

    キャンセル

  • 2019/02/10 15:10

    追記しました。これでさすがにいいでしょう?

    キャンセル

  • 2019/02/10 15:11

    > 悲しいです。

    いまさら?
    とにかく質問には完ぺきに答えました。
    聞く耳持たなかったのはあなたです。
    低評価を取り消してベストアンサーにしてください。

    キャンセル

  • 2019/02/10 15:13

    勝手に補足ですが、
    Zuishinさんの追記のコードは、「Eventの発動を非同期Task化する」というまさに要件通りのもので、それを`Read()`内で呼び出しTask.Wait()することで質問者さんの意図通りの動作をする、というもので、それにSystem.Reactiveの機能を利用しているだけです。最も合理的に書かれています。要件自体が非合理なのを置いといてですが。

    キャンセル

  • 2019/02/10 15:19

    実際に試してはいませんが、要件は実現できてそうですね。
    聞く耳は持っていて、誠実に対応したつもりですよ。質問の回答にはなっていないですけどね。
    これから確認をしますので、少々お待ちを。

    キャンセル

  • 2019/02/10 15:22

    質問がどういうものなのかわからなかったのでここで聞いて最終的にどうなれば解決なのかをはっきりさせました。
    だから質問の回答になっています。
    取り消してください。

    キャンセル

  • 2019/02/10 15:30

    すみませんが、MyReaderクラスの書き換えでないと回答ではありません。

    キャンセル

  • 2019/02/10 15:31

    あなたの MyReader クラスは動きません。
    動くと主張しているのはあなただけです。

    キャンセル

  • 2019/02/10 15:33

    > > Task.Wait でイベントを待つことができれば解決しますか?
    > イベントで渡される引数をRead側でもらえれば、Task.Wait でイベントを待つことで解決します
    > > イベントが来るまでずっと止まりますが、それができれば解決ということでいいですか?
    > イベントで渡される引数をもらえれば、止まっても大丈夫です。

    止まっても大丈夫ということをはっきり書いています。

    キャンセル

  • 2019/02/10 15:33

    え?どうなっちゃうの?

    キャンセル

  • 2019/02/10 15:35

    私はあなたの要件を細かく確認し、完璧な回答を用意しました。
    まさに要件通りのものがここにあります。
    あとは「大丈夫」と言ったあなた自身が解決すべきことです。
    私はそれで本当に大丈夫なのか何度もちゃんと確かめました。

    キャンセル

  • 2019/02/10 15:39

    > あなたの文章の間違い探しをしているわけではないです。
    > 私はawait処理をC言語のsetjmp/longjmpのようなものと捉えています。
    > そういう意味では、asyncでないという意味での同期メソッドをTask.Wait()するなら、必ずしもメインスレッドが止まるわけでもないです。Waitしてる間、メインスレッドで動作できます。

    この通り、動作することを保証しているのはあなたであり、私ではありません。

    そして私はちゃんと代替え案を二つ用意していますが、あなたはそれを無視してこの仕様に落ち着きました。ですからこの質問に対する回答はこれで間違いありません。
    私はあなたのソフトに完成まで付き合う気はありませんので、一つの質問の解決をもって解決としてください。
    それができなければあなたは Q&A サイトを使うことはできません。

    キャンセル

  • 2019/02/10 15:40

    質問の意図が分からないと言われて、それに答えると、求めている回答が変わるわけではありませんよ。
    いずれにせよ、確認は普通にするのでご安心ください。

    キャンセル

  • 2019/02/10 15:41

    ん?awaitでデッドロックするってこと?

    キャンセル

  • 2019/02/10 15:41

    イベントを Task に変換し、それが Wait できれば解決ということになっているはずなので、勝手に拡大しないでください。

    キャンセル

  • 2019/02/10 15:44

    まぁあなたはそれでいいのでしょうね。
    残念ですが、QAサイトとしてはそれではいけないと思います。

    キャンセル

  • 2019/02/10 15:47

    だめだこりゃ。

    キャンセル

  • 2019/02/10 15:48

    > Zuishinさん
    Taskを経由して同期待ちするだけのMyReaderの実装を書いてあげるのが良いと思います。自分が書くのは申し訳ないので。動くことには動きます。完全同期で。

    キャンセル

  • 2019/02/10 15:50

    tamoto さん、二種類書いていますが破棄します。

    キャンセル

  • 2019/02/10 15:51

    質問者さんへ。QAサイトは困り事を解決する場ですが、最初から不合理な要件を見かけたら、「それはおかしい」と声を上げるのもQAサイトの役割です。要件をそのまま回答することを求めるなら、それは外注に依頼するべきものです。
    例えばですが、「データベースに1万回アクセスしてから1件のデータを取りたい」っていう要件を聞いたとして、「1万回アクセスしなくてもよくない?」って声を上げるのはおかしいでしょうか?

    キャンセル

  • 2019/02/10 15:51

    だから書いてるってばw

    キャンセル

  • 2019/02/10 15:52

    要件は別におかしくなかったでしょ?

    キャンセル

  • 2019/02/10 15:52

    同期I/Oと非同期I/Oはどちらも需要はありますよ。

    キャンセル

  • 2019/02/10 15:53

    同じ実装にするためにも、非同期I/Oで同期I/Oを実装するのは普通だと思います。

    キャンセル

  • 2019/02/10 15:55

    最初から「要件がおかしい」という話をしています。
    同期IOを同期IOとして使う、非同期IOを非同期IOとして使う、はなにもおかしくありません。
    その上で、「同期IOを非同期IOに書き換える」という話ならこちらも回答できましたが、「同期IOを非同期IOを経由して同期IOとして使う」はなんだそりゃ、っていう話です。

    キャンセル

  • 2019/02/10 16:08

    同期I/Oは通常、非同期I/Oをwaitして作りますよ。そうでないと実装がバラバラになります。
    async/awaitの話ではないです。.NETの話ではないので。。。
    まぁなんでも同じなんですけど。

    キャンセル

  • 2019/02/10 16:12

    じゃあ Wait すれば?
    そこから先は他人の責任ではありません。
    自分で言い張ってることの責任は自分でとってくださいな。

    キャンセル

  • 2019/02/10 16:17

    Zuishinさんの追記のコードを参考に、ToTask()でEventからTaskを作ってください。そのTaskをWait()してください。それらをRead()の中に書いてください。これで要件を満たせますでしょうか。

    キャンセル

  • 2019/02/10 16:28

    Zuishinさんへ
    まだちゃんと仕組みが理解できてないので、何とも言えませんが、やれることはやるので心配の必要はありません。ただ、責任とかとは関係ないですよ。「要件が分からない」と言った人に説明をしただけなので。
    まぁ動く方法が見つかれば、そこからアプローチはできるはずなので、お気になさらず。

    tamotoさんへ
    今諸々の周辺技術の記事を読んでるところです。まだ仕組みがきちんと理解できていないので。
    この辺だけでもFromEventとFromEventPatternの違いがあるようです。
    できればその実装の中身も知りたいですね。なるべく急ぎますが、先は長いです。

    キャンセル

  • 2019/02/10 16:30

    心配の必要がないなら解決済みにしてください。
    私にベストアンサーを与えたくないなら自己解決でどうぞ。

    キャンセル

  • 2019/02/10 16:34

    まあこのほとんどそのままのコードを見てこれだけ時間がかかるってことは「解決できない」っていうことなんでしょうけどね。

    キャンセル

  • 2019/02/10 16:42

    どちらもまだ確認中なので、何とも言えません。
    別にそんなすぐにはできませんよ?ここでチャットしてる時間の方が長いです。

    キャンセル

  • 2019/02/10 16:44

    すぐにできますよ。
    あなたが遅いだけです。

    キャンセル

  • 2019/02/10 16:47

    そういう感覚は人それぞれだと思います。説明が大変ですね。
    でもこれReflection使ってるのかなぁ・・・(独り言)

    キャンセル

  • 2019/02/10 16:51

    リフレクションは使ってるにきまってるでしょう。
    nameof 演算子を見れば一目瞭然です。
    使わない方のオーバーロードはあなたに理解しにくいだろうから簡単な方を使ったんですよ。

    キャンセル

  • 2019/02/10 16:53

    まさかこちらまで理解できないとまでは思いませんでしたが、これ以上簡単なオーバーロードってないんですよね。

    キャンセル

  • 2019/02/10 16:58

    いやぁ十分難しいと思いますけど・・・
    いずれにしてもすぐできそうには思えないです

    キャンセル

  • 2019/02/10 17:00

    いやいやご謙遜を。
    さすがに中学生以下ということはないでしょう。

    キャンセル

  • 2019/02/10 17:17

    いやほんとにw

    キャンセル

  • 2019/02/10 17:19

    それで低評価できるはずがないじゃないですか。
    わからないのに今まで偉そうに上から目線で語ってたなんてそんな面白いこと。

    キャンセル

  • 2019/02/10 17:22

    ちょっと語尾にドヤァって入れてみますね。

    > すみませんが、Observer/Observableパターンでは解決しないと思います。(ドヤァ
    > そもそもイベントハンドラもコールバックも、Observer/Observableパターンの一種です。(ドヤァ

    キャンセル

  • 2019/02/10 17:38

    いや.NETにReactiveなんて思想があると思ってなかったですw
    Observerに反応して何コレ?大丈夫?と思ってすぐ低評価しましたw
    でもほんとコレ難しいw
    どういう仕組み?w

    キャンセル

  • 2019/02/10 17:41

    理解できたならまずは低評価を外しましょう。間違ってもいないのに低評価されるいわれはありません。

    キャンセル

  • 2019/02/10 17:49

    そこは取り敢えずデッドロック回避の方法まで検討できたからかなぁ。
    Taskをキューイングして取るくらいにしないと出来なそう。
    まず仕組みが分かってないので。。。

    キャンセル

  • 2019/02/10 17:51 編集

    デッドロック回避の方法は教えました。現に私のサンプルコードはデッドロックしないはずです。

    キャンセル

  • 2019/02/10 18:03

    ん?俺のコードだとしちゃったんだがw
    何が違うんだろw
    事前にイベント発行してるとアウトなので、ロックの中で待ったら当然デッドロックと。。。
    俺の方法ではデータの渡し方にロックが必要な方法を取っている以上、こういうのはどうしても起きるので、もうちょっとReactiveな仕組みを調べて回避しないと…

    キャンセル

  • 2019/02/10 18:11

    > Task.Wait でイベントを待つことができれば解決しますか?
    > イベントが来るまでずっと止まりますが、それができれば解決ということでいいですか?

    > 始めからそう言ってるつもりですけど。。。(ドヤァ

    Task.Wait でイベントを待つことができればデッドロックしても解決だって最初から言ってたそうですが。

    私は最初からあなたのコードそのままだとデッドロックするけどそれでいいのかって言ってるでしょ。
    tamoto さんも。

    キャンセル

  • 2019/02/10 18:17

    イベントが来ても止まってるでしょw
    デッドロックしてもいいとは一度も言ってないよ
    Monitorの代わりを作るか、別のところで同期を取って何らかの方法で受け渡すしかない。

    キャンセル

  • 2019/02/10 18:22

    イベントが来てないから止まってるんですよ。

    キャンセル

  • 2019/02/10 18:33

    デッドロックしてるのはリストをlockしてるところなので、イベント自体は来てますよ
    この中とTask.WaitするとMonitor.Waitのようには待てないのでデッドロックするということです

    キャンセル

  • 2019/02/10 18:47

    > そういう意味では、asyncでないという意味での同期メソッドをTask.Wait()するなら、必ずしもメインスレッドが止まるわけでもないです。Waitしてる間、メインスレッドで動作できます。

    なるほど、イベントは来てるし止まってないのにデッドロックすると。

    キャンセル

  • 2019/02/10 19:02

    通常はしないでしょw
    しょうもない揚げ足取りしないw
    スレッドが違うかもしれないからリソースの同期をした結果、デッドロックを起こすってだけで、awaitとは関係ないよ。
    ただ、現状の解からは俺には回避策が分かっておらず、まだ回答には至っていないだけ。
    コンストラクタで起動したタスクの結果を綺麗にReadで受け取れればいいんだけど、、、

    キャンセル

  • 2019/02/10 19:06

    で、既に解決策は教えているはずですが、これ以上何を言えと?
    コード全部私が書けと?
    あなたが書く以上、つたない部分があってもあなたのせいでしょう。

    キャンセル

  • 2019/02/10 19:08

    私が「それじゃ動かない」といくら言ってもあなたが「これで動く」とマウントポジションで言ったんだから。

    キャンセル

  • 2019/02/10 19:11

    いや、私は今確認をしてる最中なだけで何も求めてないですよ。。。
    あなたが解決済みとベストアンサーを要求してて、俺はまだそういう段階ではないと言ってて揉めているだけ。急かさず心配しないで待っててくれ、とは言ってるけども、矢継ぎ早にレスが来るので回答に追われてる状態です。

    キャンセル

  • 2019/02/10 19:14

    優先すべきことは何か今一度よく考えましょう。

    キャンセル

  • 2019/02/10 19:14

    > 私が「それじゃ動かない」といくら言ってもあなたが「これで動く」とマウントポジションで言ったんだから。
    そんなことはしてないですw

    キャンセル

  • 2019/02/10 19:16

    > そういう意味では、asyncでないという意味での同期メソッドをTask.Wait()するなら、必ずしもメインスレッドが止まるわけでもないです。Waitしてる間、メインスレッドで動作できます。

    キャンセル

  • 2019/02/10 19:18

    これは事実ですよね?
    どの辺でマウントしてるんでしょw

    キャンセル

  • 2019/02/10 19:18

    まあマウントポジションは私の脳内のあなたがしてたんですけどね。
    草を生やしながら私に間違った情報をご教授してくれてるところを見て想像しました。

    キャンセル

  • 2019/02/10 19:18

    事実ではありません。

    キャンセル

  • 2019/02/10 19:19

    現に動かないでしょ?
    何を言っているんですか?

    キャンセル

  • 2019/02/10 19:23

    UIスレッドはどうやって動いているんでしょうね。awaitしてる間、別のことしてますよね?
    あなたの理屈だとUIスレッドはawaitしてる間、終わるまで何もできなくなりますよ?

    キャンセル

  • 2019/02/10 19:26

    await と Task.Wait が同じものだと思っていたのですか。

    キャンセル

  • 2019/02/10 19:28

    え?ほぼ同じじゃないの?違うの?
    だから話が噛み合わなかったのか

    キャンセル

  • 2019/02/10 19:28

    asyncとかおまけだと思ってました

    キャンセル

  • 2019/02/10 19:30

    ほぼ同じ物を新しく作る意味などないでしょう。ほぼ同じ物があれほど話題になったわけもないでしょう。

    全然違います。

    キャンセル

  • 2019/02/10 19:51

    これはやばそうだね。。。
    実装する前にしっかり調べておかないといけなそう。まずはそっち調べるわw

    キャンセル

  • 2019/02/10 21:31

    調べれば調べるほど、いろんな記事と違う現象が出てきて、よく分からない。
    現状だとデッドロックするという現象は出てないし、Task.Runの直前直後にWaitを入れると、Task.Runの前後と中が全て同一のスレッドで実行される。
    よく分からず、追うべきソースもないので、一旦ここでは棚上げにしてもいい?

    キャンセル

  • 2019/02/11 06:22

    ■Task.Wait()のデッドロック問題について

    現状だと、Task.Waitと同じスレッドでTask.Runの中のスレッドが動作していたりすることもあり、Wait中にスレッドが使用されている形跡がある。
    また、Task.Waitをawaitで待ち合わせるとデッドロックするとの記事があったが、その記事通りの実装、かつ同一スレッドでの待ち合わせで固まっていなかった。
    なぜawaitと違う挙動という言及がされているのか、よくわからない。勘違いかもしれないので、とりあえず棚上げ。

    ■System.Reactive拡張を使用したeventの待ち合わせについて

    MyReader.ReadにてSystem.Reactive拡張を使用したeventの待ち合わせを実装してみたところ、いくつか問題があった。

    (1)そのままの形で使用するとなると、単純にタスクに変換してWaitすることになるが、その際にデータの取得が出来ず、イベントでの呼び出しとスレッドが違う可能性があるため、ロックが必要になる。ロックが必要になると、Monitor.Waitのような機構がないため、そのままではデッドロックしてしまう。せいぜいTask.Wait直前/直後にロックを分けることで、回避することになるが、直前のロック中にイベントが発生した場合、ちゃんとイベントが拾えるとは限らないコードになった。

    (2)(1)のデータ同期問題をクリアするため、Subscribeして、eventの待ち合わせを実装してみたが、Readメソッド内でeventが発生しないケースで、イベントの取りこぼしが起きた。原因はSubscribeをReadメソッド内でしていたからだが、Subscribeをコンストラクタ側ですると、Readメソッド外でイベントが発生してしまい、意味がなかった。回避手段としては、ReplaySubjectの使用を検討したが、これもConnectが一度しか出来ず、一度Connectすると全部流れてしまうので、最初の1つだけ処理して、他は全部残す的な使い方ができなかった。Subjectに逐一Replayしたとしても、結局1つずつ取得する手段はないので、どこかに貯める必要があると判断。こうなるとデータ同期問題を回避できないので、この方法は自分の現状の知識では実現不可と判断した。

    結果、async/await/Taskでイベントを待つには、少なくとも力不足、と考える。
    現実的には、現状の実力ではMonitorで同期する方が、簡潔に書ける、と思う。
    Rx大好きな人なら美しく何とかしちゃう可能性もある、とも思うw
    懸念点は、Task.Waitを使っている点。棚上げ中となっている。

    (1)については、ソースが気になる人がいるかもなので、以下が現実装。
    残念ながら、同期が不完全でNGです。

    using System;
    using System.Collections.Generic;
    using System.Reactive.Linq;
    using System.Reactive.Threading.Tasks;
    using System.Threading.Tasks;

    namespace EventAsyncAwait
    {
    interface Reader
    {
    int Read();
    }

    class MyEventArgs
    {
    public int val { get; set; }
    }

    class MyReader : Reader
    {
    List<int> buffer = new List<int>();

    public int Read()
    {
    int result = 0;
    bool needWait;
    lock (buffer)
    {
    needWait = (buffer.Count == 0);
    }
    if (needWait)
    {
    Task task = Observable.FromEventPattern<MyEventArgs>(
    h => _parent.Event += h,
    h => _parent.Event -= h)
    .Take(1)
    .ToTask();
    task.Wait();
    }
    lock (buffer)
    {
    result = buffer[0];
    buffer.RemoveAt(0);
    }
    return result;
    }

    EventHolder _parent;

    public MyReader(EventHolder parent)
    {
    _parent = parent;
    parent.Event += (object sender, MyEventArgs e) =>
    {
    lock (buffer)
    {
    buffer.Add(e.val);
    }
    };
    }
    }

    class EventHolder
    {
    public event EventHandler<MyEventArgs> Event;

    public void Fire(int v)
    {
    Event(this, new MyEventArgs() { val = v });
    }
    }

    class Program
    {
    static EventHolder holder = new EventHolder();

    static void Main(string[] args)
    {
    MyReader reader = new MyReader(holder);
    Task.Run(async () =>
    {
    await Task.Delay(3000);
    holder.Fire(5);
    });
    Console.WriteLine(reader.Read());
    }
    }
    }

    キャンセル

  • 2019/02/11 06:35

    追伸です。
    (1)でリストをスレッドセーフにするという手もありますが、いずれにしてもWaitの境界で通知の有無が微妙なので、同じですよ。念の為。

    キャンセル

  • 2019/02/11 13:57

    この程度の物は一人で作れるのでご教授いただかなくて大丈夫です。二種類作って捨てました。

    キャンセル

  • 2019/02/11 14:13

    いや、証拠もなくNGにできないので、出しただけですよ
    あと「ご教授」ではなく「ご教示」ですって昔言ってる人がいたのを思い出しました

    キャンセル

  • 2019/02/11 14:21

    何の証拠か知りませんが「あなたが」できないのは私のせいではありません。
    私はできましたから。

    キャンセル

  • 2019/02/11 14:30

    別にあなたが正解を持っていなくても、別に誰かが困るわけでもないので、気にもとめませんよ。
    ただ、できた証拠がない、もしくはどこがあなたのできたものと違っていて、確実に出来ていたという証明がない限り、このQAサイトでいえば、残念ですが、「あなたも」できていないと考えざるを得ません。
    これで終わりにしましょうかね。

    キャンセル

  • 2019/02/11 14:36

    自己解決をどうぞ。

    キャンセル

  • 2019/02/11 14:38

    できないという結論でどうぞ。

    キャンセル

  • 2019/02/11 14:41

    これだけのヒントをあげたのに。ほぼ答えじゃんというのが私の感想です。

    キャンセル

  • 2019/02/11 14:55

    現状の結論ではできないという結果であり、それは悪魔の証明になるので回答にはならないです。
    Rxのライブラリに自作の機能を追加か、現状のイベントハンドラと同等の同期を入れれば出来る方法があるのかもしれませんが、残念ですがZuishinさんの方法では不可能です。
    .NETのRx系統のライブラリの存在は知らなかったので、個人的に得られるものはありましたが、質問に対する回答としては、そうなります。ヒントには気付きませんでしたが、Zuishinさん文句が多いし、言い掛かりばかりで証拠の提示がないのがほとんどでしたね。次回があるなら、もう少し文量に見合った、技術的にまともなやり取りができればいいなと思っています。

    というわけで、すみませんが、このまま放置になります。私もやることがあるので、そろそろいいですよね。

    キャンセル

  • 2019/02/11 14:58

    質問を放置せずきちんと解決してください。

    キャンセル

  • 2019/02/11 15:03

    https://ja.wikipedia.org/wiki/%E6%82%AA%E9%AD%94%E3%81%AE%E8%A8%BC%E6%98%8E#%E6%B6%88%E6%A5%B5%E7%9A%84%E4%BA%8B%E5%AE%9F%E3%81%AE%E8%A8%BC%E6%98%8E

    キャンセル

  • 2019/02/11 15:43

    ご教授いただかなくても悪魔の証明くらい知ってます。あなたにとっては最近覚えた言葉かもしれませんが、一般常識です。

    数分で解決可能な問題を質問サイトを使ってまでこれだけ時間と労力を使って解決できないというのは異常ですね。質問者が他の人なら悪魔の証明を持ち出すまでもなくすぐに解決したことでしょう。

    解決できる回答者が回答欄にもコメント欄にも修正依頼欄にもこんなにいるのになぜ解決できなかったかわかりませんか?

    これだけ時間をかけて何も得られないあなたは果たして自分の思うほど有能なんですかね?

    キャンセル

  • 2019/02/11 15:50

    現状事実の積み上げで解決できていないのですよ。。。説明何度もしましたよね?
    悪魔の証明を知っているなら、続ける意味がありません。
    NGにされたからって、いい加減言い掛かりばかり言うのはやめませんか?
    何の話をしてるのでしょう?大丈夫ですか?

    キャンセル

  • 2019/02/11 15:55

    続ける意味がないならやめたらいいのでは?
    大丈夫ですか?

    キャンセル

  • 2019/02/11 15:56

    数分で解決可能な問題を質問サイトを使ってまでこれだけ時間と労力を使って解決できないというのは異常ですね。質問者が他の人なら悪魔の証明を持ち出すまでもなくすぐに解決したことでしょう。

    キャンセル

  • 2019/02/11 16:05

    ここ基本質問者の返事で終わる欄なので、申し訳程度に付き合ってあげてるだけですよ。
    同じことを繰り返してまで、言うことなんですかね。
    反証もないあなたの脳内の結論に興味はないんですよ。この辺で終わりにしませんか?

    キャンセル

  • 2019/02/11 16:13

    なんで続けてるんですか?
    大丈夫ですか?

    キャンセル

  • 2019/02/11 16:14

    これだけ時間をかけて何も得られないあなたは果たして自分の思うほど有能なんですかね?

    キャンセル

  • 2019/02/11 16:17

    ちょw 何これw どうしたの?
    俺が有能かどうかなんてどうでもいいよw
    構ってほしいの?

    キャンセル

  • 2019/02/11 16:19

    え?
    いまさら?

    キャンセル

  • 2019/02/11 16:22

    マジで構ってほしいだけなら、こんなとこ使わないでw
    チャットがしたいなら行けるとこなら行ってあげてもいいし。。。
    まぁそんなに頻繁にはできんと思うけど
    OK?

    キャンセル

  • 2019/02/11 16:25 編集

    そんなこと言わず構ってくださいよ。私、タフでプライドの高い無能大好きなんです。あなたがそうならいいなと思ってます。

    キャンセル

  • 2019/02/11 16:27

    ちょっとさすがにこの場でこれはいかん。通報しといた。

    キャンセル

  • 2019/02/11 16:32

    残念。タフじゃなかった。ただのプライドの高

    キャンセル

  • 2019/02/11 16:36

    いや別にココでなければ行けるとこなら行くって言ってるんだけども。。。
    仕方なくね?
    プライドなんてミジンコ並なんだが

    キャンセル

  • 2019/02/11 16:40

    残念。プライドもミジンコだった。ただの無

    キャンセル

  • 2019/02/11 16:42

    まあそれなら用はないんでつきまとわないでください。

    キャンセル

  • 2019/02/11 16:44

    期待に添えなくて悪いね
    じゃあこれで終わりってことで

    キャンセル

  • 2019/02/11 16:45

    気持ち悪いんで、はなしかけないで。

    キャンセル

+2

こんにちは。

そのMain関数を別関数にしてasync関数とし、Task.Runの中でReadを呼び出し、そのTask.Runをawaitすれば良いはずです。int ret=await Task<int>.Run(()=>{return reader.Read();});のようなイメージです。

async関数を呼び出すと、awaitにてTaskを起動したら直ぐにasync関数の呼び出し元に戻ってきます。
Task.Runはサブ・スレッドで実行を開始しReadが終わればそのままawaitの次から実行を継続します。
aync関数から戻った地点と、awaitの次の地点がそれぞれ異なるスレッドで実行されています。これらを同期させる仕組みも何か必要と思います。

以上の動作はコンソール・アプリの場合です。GUIアプリの場合も見た目には良く似た動作をしますが、実際の処理内容はかなり異なります。最大の差はawaitの次をメッセージ・ループを回しているスレッド(通常はメイン・スレッド)で実行することです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/10 18:21

    う〜ん、それではReadでどう待つと言っているのでしょうか?
    根本的なところが違っているような…

    キャンセル

  • 2019/02/10 20:26

    提示されているRead()関数は同期的にイベント発生を待っていると思います。そのままで良いですよ。
    同期関数をTask.Runで呼び出して非同期化するという、async/awaitの定番的な使い方の1つです。
    https://chronoir.net/cs-async-await-taskrun/

    キャンセル

  • 2019/02/10 20:27

    ああ、ごめんなさい。Zuishinさんとのやりとりを見ていませんでした。
    私はこれにて降りさせて頂きます。

    キャンセル

  • 2019/02/10 22:31

    一応書いておくと、そのままで何か不都合のある動作をしているわけではありません。
    MyReaderクラスのReadメソッドの中身をasync/await/Taskで実装できないかを検討したく、呼び出し方法部分は検討対象ではない説明用の部品なので、申し訳ありませんが、このままにします。

    キャンセル

-6

とりあえずZuishinさんのタスクをキューに入れたバージョンで、相変わらずロックなどの同期は必要なもののMonitorを使用しない形には出来るので、書いておきます。コメントで(1)と(2)を出していましたが、その間といったところのものを作ってみました。
エラー処理とメモリが心配な作りになっていますが、今の所他に問題は見つかっていません。一応ちゃんと書いておくと、控えめに言っても多分あまりRectiveな実装ではなく、また元の実装と比較して効率がいいわけでもありませんが、aync/awaitで非同期のReadメソッドが簡単に作成できます。

数日待って問題の指摘がなければ解決とさせて頂きます。

using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading.Tasks;

namespace EventAsyncAwait
{
    interface Reader
    {
        int Read();
    }

    class MyEventArgs
    {
        public int val { get; set; }
    }

    class MyReader : Reader
    {
        public int Read()
        {
            TaskContext context;
            lock (_queue)
            {
                context = _queue.Dequeue();
            }
            context.Task.Wait();
            context.Observer.Dispose();
            return context.Args.EventArgs.val;
        }

        class TaskContext
        {
            public EventPattern<MyEventArgs> Args { set; get; }
            public Task Task { set; get; }
            public IDisposable Observer { set; get; }
        }

        Queue<TaskContext> _queue = new Queue<TaskContext>();

        private void CreateTask(IObservable<EventPattern<MyEventArgs>> subject)
        {
            var observable = subject.Take(1);
            var elm = new TaskContext();
            elm.Observer = observable.Subscribe((args) => {
                elm.Args = args;
                CreateTask(subject);
            });
            elm.Task = observable.ToTask();
            lock(_queue)
            {
                _queue.Enqueue(elm);
            }
        }

        public MyReader(EventHolder parent)
        {
            CreateTask(
                Observable.FromEventPattern<MyEventArgs>(
                    h => parent.Event += h,
                    h => parent.Event -= h));
        }
    }

    class EventHolder
    {
        public event EventHandler<MyEventArgs> Event;

        public void Fire(int v)
        {
            Event(this, new MyEventArgs() { val = v });
        }
    }

    class Program
    {
        static EventHolder holder = new EventHolder();

        static void Main(string[] args)
        {
            MyReader reader = new MyReader(holder);
            Task.Run(async () =>
            {
                await Task.Delay(3000);
                holder.Fire(5);
            });
            Console.WriteLine(reader.Read());
        }
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

check解決した方法

-7

さらにSystem.Reactive拡張を読んでみたのですが、肝心の待ち合わせの部分のソースを見つけられなくてできませんでした。恐らく、async/await/Task+言語仕様だけで待ち合わせを実現したのではなく、何らかの同期機構をセットで使用したのではないか?と推測しています。

そこで、今回はMonitorを使用して待ち合わせた実装を、もう一つの回答で書いたものと同様な、汎用なイベントハンドラをラップする実装に組み込んでみました。ようはSystem.Reactive拡張を真似て、Monitorを使う待ち合わせを使ったasync/awaitの簡易版汎用実装をしてみたということです。なるべくメモリ効率を良くして、コンテキストスイッチを抑える実装にはしてみたつもりです。

なお、前回同様エラーハンドリングはないので、そのまま使っちゃダメですよ。
これを数日様子見して解決の判断をするつもりです。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace EventAsyncAwait
{
    interface Reader
    {
        int Read();
    }

    class MyEventArgs
    {
        public int val { get; set; }
    }

    class EventAwaitableFunction<A>
    {
        public class EventContext
        {
            public object Sender { get; set; }
            public A Args { get; set; }
        }

        public delegate void AddHandler(EventHandler<A> h);
        private List<EventContext> buffer = new List<EventContext>();

        public EventAwaitableFunction(AddHandler add)
        {
            add((s, a) =>
            {
                var elm = new EventContext();
                elm.Sender = s;
                elm.Args = a;
                lock (buffer)
                {
                    buffer.Add(elm);
                    Monitor.PulseAll(buffer);
                }
            });
        }

        public async Task<EventContext> GetFirstEvent()
        {
            EventContext result = null;
            lock (buffer)
            {
                if (buffer.Count > 0)
                {
                    result = buffer[0];
                    buffer.RemoveAt(0);
                }
            }
            if (result == null)
            {
                await Task.Run(() =>
                {
                    lock (buffer)
                    {
                        while (buffer.Count == 0)
                        {
                            Monitor.Wait(buffer);
                        }
                        result = buffer[0];
                        buffer.RemoveAt(0);
                    }
                });
            }
            return result;
        }
    }

    class MyReader : Reader
    {
        EventAwaitableFunction<MyEventArgs> func;
        List<int> buffer = new List<int>();

        public int Read()
        {
            return ReadAsync().Result.Args.val;
        }

        public async Task<EventAwaitableFunction<MyEventArgs>.EventContext> ReadAsync()
        {
            return await func.GetFirstEvent();
        }

        public MyReader(EventHolder parent)
        {
            func = new EventAwaitableFunction<MyEventArgs>(h => parent.Event += h);
        }
    }

    class EventHolder
    {
        public event EventHandler<MyEventArgs> Event;

        public void Fire(int v)
        {
            Event(this, new MyEventArgs() { val = v });
        }
    }

    class Program
    {
        static EventHolder holder = new EventHolder();

        static void Main(string[] args)
        {
            MyReader reader = new MyReader(holder);
            Task.Run(async () =>
            {
                await Task.Delay(3000);
                holder.Fire(5);
            });
            Console.WriteLine(reader.Read());
        }
    }
}


追記)
SynchronizationContextによっては、実行スレッドによりawait後のスレッドに制約がかかるため、Task.Waitと競合してデッドロックすることがあります。
コンソールアプリ以外で使用するケースでは、そのようなケースが起こりうるので、ご注意ください。
例えば、これ100msくらいで終わるからUIスレッドで実行しよう、とかすると、100ms経過しても返ってこず、固まるということです。確実にUIスレッド以外から実施してください。ASP.NETでの回避方法は未確認です。
ConfigureAwaitによる回避は↓の記事にもある通りやめるべきだと思います。

デッドロックについては、↓が詳しいです。
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

SynchronizationContextについては、↓参照です。
https://msdn.microsoft.com/magazine/gg598924.aspx

最後に、納得の行かない人もいるかもしれないので、書いておきますが、待ち合わせという性格上、このI/Fをawaitで実装する限り、Task.Waitは仕方のない選択です。有効なケースであれば問題はないので、個人的に分かって使う分には益がある、と考えています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/13 14:29

    ほかの人のために書いておきます。
    仕方のない選択ではありません。
    無知故の間違った結論です。

    キャンセル

同じタグがついた質問を見る

  • C#

    8245questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。