ゲッターまたはセッターから非同期メソッドを呼び出す方法は?


223

C#でゲッターまたはセッターから非同期メソッドを呼び出す最もエレガントな方法は何でしょうか?

ここに私自身を説明するのに役立ついくつかの疑似コードがあります。

async Task<IEnumerable> MyAsyncMethod()
{
    return await DoSomethingAsync();
}

public IEnumerable MyList
{
    get
    {
         //call MyAsyncMethod() here
    }
}

4
なぜでしょうか。プロパティは、通常、ほとんど(または少なくとも非常に迅速に)作業を実行する必要があるという点で、フィールドのようなものを模倣することになっています。長期実行プロパティがある場合は、それをメソッドとして記述して、呼び出し元がより複雑な作業の本体であることを認識できるようにすることをお勧めします。
James Michael Hare

@ジェームズ:まさにその通りです。これがCTPで明示的にサポートされていなかった理由です。とは言っても、Task<T>すぐに返されるtypeのプロパティを常に作成し、通常のプロパティセマンティクスを設定し、必要に応じて非同期で処理することができます。
リードコプシー、2011

17
@James私の必要性は、MvvmとSilverlightを使用することから生じます。データの読み込みが遅延して行われるプロパティにバインドできるようにしたいのですが。私が使用しているComboBox拡張クラスでは、InitializeComponent()ステージでバインディングを実行する必要がありますが、実際のデータのロードはかなり後で発生します。できるだけ少ないコードで達成しようとすると、getterとasyncは完璧な組み合わせのように感じられます。
Doguhan Uluca


ジェームズとリード、あなたはいつもエッジケースがあることを忘れているようです。WCFの場合、プロパティに配置されているデータが正しいことを確認したかったので、暗号化/復号化を使用して確認する必要がありました。私が復号化に使用する関数は、たまたまサードパーティベンダーの非同期関数を使用しています。(私がここでできることはそれほど多くありません)。
RashadRivera 2017

回答:


211

何もありません技術的なことを理由async性質はC#で許可されていませんが。「非同期プロパティ」は矛盾しているため、意図的な設計決定でした。

プロパティは現在の値を返す必要があります。バックグラウンド操作を開始するべきではありません。

通常、誰かが「非同期プロパティ」を必要とする場合、実際に必要なのは次のいずれかです。

  1. 値を返す非同期メソッド。この場合、プロパティをasyncメソッドに変更します。
  2. データバインディングで使用できますが、非同期で計算/取得する必要がある値。この場合、コンテナasyncオブジェクトのファクトリメソッドを使用するか、async InitAsync()メソッドを使用します。データにバインドさdefault(T)れた値は、値が計算または取得されるまでになります。
  3. 作成にはコストがかかりますが、将来の使用のためにキャッシュする必要がある値。この場合、AsyncLazy 私のブログまたはAsyncExライブラリから使用ます。これはあなたにawait有能な財産を与えるでしょう。

更新:私は最近の「非同期OOP」ブログ投稿の1つで非同期プロパティを取り上げています。


ポイント2では、プロパティを設定すると、基になるデータが(コンストラクター内だけでなく)再度初期化されるという通常のシナリオは考慮されません。Nito AsyncExを使用するDispatcher.CurrentDispatcher.Invoke(new Action(..)か、使用する以外に他の方法はありますか?
ジェラール

@ジェラード:その場合、ポイント(2)が機能しない理由はわかりません。を実装してINotifyPropertyChangedから、古い値を返すかdefault(T)、非同期更新の実行中に戻すかを決定します。
Stephen Cleary 2013年

1
@Stephan:わかりましたが、セッターで非同期メソッドを呼び出すと、CS4014の「待たない」という警告が表示されます(またはFramework 4.0でのみですか?)。そのような場合、その警告を抑制するようにアドバイスしますか?
ジェラール

@Gerard:私の最初の推奨事項はNotifyTaskCompletion、私のAsyncExプロジェクトのを使用することです。または、独自に構築することもできます。それほど難しいことではありません。
Stephen Cleary 2013年

1
@Stephan:わかりました。おそらく、この非同期のdatabinding-viewmodel-scenarioに関する素晴らしい記事が用意されています。例えば{Binding PropName.Result}、私にバインドすることは私が見つけるのは簡単ではありません。
ジェラール

101

非同期プロパティのサポートはなく、非同期メソッドのみなので、非同期に呼び出すことはできません。そのため、2つのオプションがあり、どちらもCTPの非同期メソッドは実際には単に返すTask<T>か、またはTask

// Make the property return a Task<T>
public Task<IEnumerable> MyList
{
    get
    {
         // Just call the method
         return MyAsyncMethod();
    }
}

または:

// Make the property blocking
public IEnumerable MyList
{
    get
    {
         // Block via .Result
         return MyAsyncMethod().Result;
    }
}

1
お返事ありがとうございます。オプションA:タスクを返すことは、拘束力のある目的では実際にはうまくいきません。オプションB:.Resultは、お伝えしたように、UIスレッド(Silverlight内)をブロックするため、バックグラウンドスレッドで実行する操作が必要です。このアイデアで実行可能なソリューションを考え出せるかどうかを確認します。
Doguhan Uluca

3
@duluca:private async void SetupList() { MyList = await MyAsyncMethod(); } 非同期操作が完了するとすぐに、次のようなメソッドを使用してMyListを設定(INPCを実装している場合は自動的にバインド)することもできます...
Reed Copsey

プロパティは、ページリソースとして宣言したオブジェクト内にある必要があるため、この呼び出しをゲッターから開始する必要が本当にありました。私が思いついた解決策に対する私の答えを見てください。
Doguhan Uluca

1
@duluca:それは、事実上、私があなたに提案したことでした...実現します、しかし、これに何度もすばやくTitleにアクセスすると、現在のソリューションは、複数の呼び出しを同時に引き起こしますgetTitle()...
Reed Copsey

非常に良い点です。私の特定のケースでは問題ではありませんが、isLoadingのブール値チェックで問題が修正されます。
Doguhan Uluca

55

分離されたアーキテクチャーのため、getメソッドから呼び出す呼び出しが本当に必要でした。そこで、以下の実装を思いつきました。

使用法: タイトルはViewModelまたはページリソースとして静的に宣言できるオブジェクトにあります。これにバインドすると、getTitle()が戻るときに、UIをブロックせずに値が入力されます。

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

9
Win8 RPの2012年7 月18日から更新Dispatcherの呼び出しを次のように変更する必要があり ます。
アントンシジコフ

7
@ChristopherStevenson、私もそう思いましたが、そうではないと思います。ゲッターはファイアアンドフォーゲットとして実行されているため、完了時にセッターを呼び出さないと、ゲッターが実行を完了したときにバインディングが更新されません。
Iain、

3
いいえ、競合状態はありましたが、「RaisePropertyChanged( "Title")」のため、ユーザーには表示されません。完了する前に戻ります。ただし、完了後はプロパティを設定します。これにより、PropertyChangedイベントが発生します。バインダーはプロパティの値を再度取得します。
Medeni Baykal

1
基本的に、最初のゲッターはnull値を返し、次に更新されます。毎回getTitleが呼び出されるようにしたい場合、不良ループが発生する可能性があることに注意してください。
tofutim

1
また、その非同期呼び出しの例外はすべて完全に飲み込まれることにも注意してください。アプリケーションのハンドルされていない例外ハンドラがあれば、それらに到達しません。
Philter

9

最初にnullが返されてから実際の値が取得されるのを待つことができると思うので、Pure MVVM(たとえばPCLプロジェクト)の場合、次の方法が最もエレガントな解決策だと思います。

private IEnumerable myList;
public IEnumerable MyList
{
  get
    { 
      if(myList == null)
         InitializeMyList();
      return myList;
     }
  set
     {
        myList = value;
        NotifyPropertyChanged();
     }
}

private async void InitializeMyList()
{
   MyList = await AzureService.GetMyList();
}

3
これはコンパイラ警告を生成しませんCS4014: Async method invocation without an await expression
Nick

6
ことは非常にこのアドバイスを以下に懐疑的。このビデオを見て、気をつけてください:channel9.msdn.com/Series/Three-Essential-Tips-for-Async/…
Contango 2015年

1
"async void"メソッドの使用は避けるべきです!
SuperJMN 2017年

1
すべての叫びには賢明な返信が必要です。@ SuperJMNに理由を説明してください。
ファンパブロガルシアコエロ2017年

1
@Contango良いビデオ。「async voidトップレベルのハンドラーなどにのみ使用する」と彼は言う。私はこれが「そして彼らの好き」とみなされるかもしれないと思います。
HappyNomad 2017

7

次のTaskように使用できます:

public int SelectedTab
        {
            get => selected_tab;
            set
            {
                selected_tab = value;

                new Task(async () =>
                {
                    await newTab.ScaleTo(0.8);
                }).Start();
            }
        }

5

.GetAwaiter()。GetResult()がまさにこの問題の解決策だと思いました。例えば:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            _Title = getTitle().GetAwaiter().GetResult();
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

5
これは単にブロックするのと同じです.Result-非同期ではなく、デッドロックを引き起こす可能性があります。
McGuireV10

IsAsync = Trueを追加する必要があります
Alexsandr Ter

私の回答に対するフィードバックに感謝します。私は誰かがこのデッドロックが実際に動作しているのを見ることができる例を提供することを本当に
望ん

2

「非同期プロパティ」はビューモデルにあるため、AsyncMVVMを使用できます。

class MyViewModel : AsyncBindableBase
{
    public string Title
    {
        get
        {
            return Property.Get(GetTitleAsync);
        }
    }

    private async Task<string> GetTitleAsync()
    {
        //...
    }
}

同期コンテキストとプロパティ変更通知が処理されます。


プロパティである必要があります。
Dmitry Shechtman、2015

申し訳ありませんが、多分私はこのコードの要点を逃しました。詳しく説明していただけますか?
Patrick Hofman、2015

プロパティは、定義によりブロックされています。GetTitleAsync()は、シンタックスシュガーの「非同期ゲッター」として機能します。
Dmitry Shechtman、2015

1
@DmitryShechtman:いいえ、ブロックする必要はありません。これがまさに変更通知とステートマシンの目的です。そして、それらは当然のことながらブロックしていません。それらは定義により同期です。これはブロッキングと同じではありません。「ブロッキング」とは、重い作業を実行し、実行にかなりの時間を消費する可能性があることを意味します。これは、まさにプロパティがすべきではないことです。
ケツァルコアトル2018

1

ネクロマンシング。
.NET Core / NetStandard2では、次のNito.AsyncEx.AsyncContext.Run代わりに使用できますSystem.Windows.Threading.Dispatcher.InvokeAsync

class AsyncPropertyTest
{

    private static async System.Threading.Tasks.Task<int> GetInt(string text)
    {
        await System.Threading.Tasks.Task.Delay(2000);
        System.Threading.Thread.Sleep(2000);
        return int.Parse(text);
    }


    public static int MyProperty
    {
        get
        {
            int x = 0;

            // /programming/6602244/how-to-call-an-async-method-from-a-getter-or-setter
            // /programming/41748335/net-dispatcher-for-net-core
            // https://github.com/StephenCleary/AsyncEx
            Nito.AsyncEx.AsyncContext.Run(async delegate ()
            {
                x = await GetInt("123");
            });

            return x;
        }
    }


    public static void Test()
    {
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
        System.Console.WriteLine(MyProperty);
        System.Console.WriteLine(System.DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss.fff"));
    }


}

System.Threading.Tasks.Task.Runまたはを選択しただけSystem.Threading.Tasks.Task<int>.Runでは機能しません。


-1

以下の私の例は@ Stephen-Clearyのアプローチに従うかもしれないと思いますが、コード化された例を挙げたかったのです。これは、Xamarinなどのデータバインディングコンテキストで使用するためのものです。

クラスのコンストラクター-または実際に、それが依存する別のプロパティのセッター-は、待機またはブロックを必要とせずにタスクの完了時にプロパティを設定する非同期voidを呼び出すことができます。最終的に値を取得すると、NotifyPropertyChangedメカニズムを介してUIを更新します。

コンストラクターからaysnc voidを呼び出すことの副作用についてはよくわかりません。おそらく、コメンターはエラー処理などについて詳しく説明します。

class MainPageViewModel : INotifyPropertyChanged
{
    IEnumerable myList;

    public event PropertyChangedEventHandler PropertyChanged;

    public MainPageViewModel()
    {

        MyAsyncMethod()

    }

    public IEnumerable MyList
    {
        set
        {
            if (myList != value)
            {
                myList = value;

                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("MyList"));
                }
            }
        }
        get
        {
            return myList;
        }
    }

    async void MyAsyncMethod()
    {
        MyList = await DoSomethingAsync();
    }


}

-1

この問題に遭遇したとき、セッターまたはコンストラクターのいずれかから非同期メソッドの同期を実行しようとすると、UIスレッドでデッドロックが発生し、イベントハンドラーを使用すると、一般的な設計に必要な変更が多すぎます。
多くの場合、解決策は、暗黙的に発生させたいことを明示的に書き込むことでした。これは、別のスレッドに操作を処理させ、メインスレッドに操作の完了を待機させることでした。

string someValue=null;
var t = new Thread(() =>someValue = SomeAsyncMethod().Result);
t.Start();
t.Join();

私はフレームワークを悪用していると主張するかもしれませんが、それは機能します。


-1

すべての回答を確認しましたが、パフォーマンスに問題があります。

たとえば:

string _Title;
public string Title
{
    get
    {
        if (_Title == null)
        {   
            Deployment.Current.Dispatcher.InvokeAsync(async () => { Title = await getTitle(); });
        }
        return _Title;
    }
    set
    {
        if (value != _Title)
        {
            _Title = value;
            RaisePropertyChanged("Title");
        }
    }
}

Deployment.Current.Dispatcher.InvokeAsync(async()=> {Title = await getTitle();});

良い答えではないディスパッチャーを使用してください。

しかし、簡単な解決策があります、それをしてください:

string _Title;
    public string Title
    {
        get
        {
            if (_Title == null)
            {   
                Task.Run(()=> 
                {
                    _Title = getTitle();
                    RaisePropertyChanged("Title");
                });        
                return;
            }
            return _Title;
        }
        set
        {
            if (value != _Title)
            {
                _Title = value;
                RaisePropertyChanged("Title");
            }
        }
    }

関数が非同期の場合は、getTitle()ではなくgetTitle()。wait()を使用してください
Mahdi Rastegari

-4

プロパティを次のように変更できます Task<IEnumerable>

そして次のようなことをしてください:

get
{
    Task<IEnumerable>.Run(async()=>{
       return await getMyList();
    });
}

そして、それをMyListのように使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.