web-dev-qa-db-ja.com

コンストラクタで非同期メソッドを呼び出す?

まとめ:コンストラクタの中で非同期メソッドを呼びたいのですが。これは可能ですか?

Details:私はJSONデータを解析するgetwritings()というメソッドを持っています。 asyncメソッドでgetwritings()を呼び出し、その左側にawaitを配置するだけですべてがうまくいきます。しかし、私が自分のページでLongListViewを作成してそれを移植しようとすると、getWritings()が驚くべきことにnullを返し、LongListViewが空であることがわかりました。

この問題に対処するために、戻り値の型getWritings()Task<List<Writing>>に変更してから、getWritings().Resultを介してコンストラクターで結果を取得しました。しかし、それをするとIスレッドをブロックしてしまいます

public partial class Page2 : PhoneApplicationPage
{
    List<Writing> writings;

    public Page2()
    {
        InitializeComponent();
        getWritings();
    }

    private async void getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}
144

最善の解決策は、ダウンロードの非同期性を認識し、それを設計することです。

つまり、データのダウンロード中にアプリケーションがどのように見えるかを決めます。ページコンストラクタにthat viewを設定させ、ダウンロードを開始してください。ダウンロードが完了したらpdateデータを表示するページです。

私は 非同期コンストラクタ についてのブログ記事を持っています。また、いくつかのMSDNの記事。 1つは 非同期データバインディング (MVVMを使用している場合)、もう1つは 非同期ベストプラクティス (つまりasync voidは避けるべきです)。

96
Stephen Cleary

このようにすることもできます。

Task.Run(() => this.FunctionAsync()).Wait();
65
Peter Stegnar

このような問題を解決するために使ってきたパターンを共有したいと思います。それはかなりうまくいくと思います。もちろん、それはあなたがコンストラクタを呼び出すものを制御する場合にのみ機能します。以下の例

public class MyClass
{
    public static async Task<MyClass> Create()
    {
        var myClass = new MyClass();
        await myClass.Initialize();
        return myClass;
    }

    private MyClass()
    {

    }

    private async Task Initialize()
    {
        await Task.Delay(1000); // Do whatever asynchronous work you need to do
    }
}

基本的には、コンストラクタを非公開にし、MyClassのインスタンスの作成を担当する独自のpublic static asyncメソッドを作成します。コンストラクタを非公開にし、静的メソッドを同じクラス内に保持することで、適切な初期化メソッドを呼び出さずにこのクラスのインスタンスを "誤って"作成することができるようになりました。オブジェクトの作成に関するすべてのロジックは、クラス内(静的メソッド内)にまだ含まれています。

var myClass1 = new MyClass() // Cannot be done, the constructor is private
var myClass2 = MyClass.Create() // Returns a Task that promises an instance of MyClass once it's finished
var myClass3 = await MyClass.Create() // asynchronously creates and initializes an instance of MyClass

現在のシナリオで実装すると、次のようになります。

public partial class Page2 : PhoneApplicationPage
{
    public static async Task<Page2> Create()
    {
        var page = new Page2();
        await page.getWritings();
        return page;
    }

    List<Writing> writings;

    private Page2()
    {
        InitializeComponent();
    }

    private async Task getWritings()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");

            writings.Add(writing);
        }

        myLongList.ItemsSource = writings;
    }
}

そして代わりに

var page = new Page2();

あなたはやっているだろう

var page = await Page2.Create();
45
Shazi

これを交換してみてください。

myLongList.ItemsSource = writings;

これとともに

Dispatcher.BeginInvoke(() => myLongList.ItemsSource = writings);
4

簡単に言えば、Stephen Clearyを参照してください https://stackoverflow.com/a/23051370/2670

作成ページでコンストラクタ内にタスクを作成し、それらのタスクをクラスメンバーとして宣言するか、タスクプールに入れる必要があります。

あなたのデータはこれらのタスクの間にフェッチされます、しかしこれらのタスクはコード、すなわちいくつかのUI操作、すなわちOkクリックなどで待っているべきです。

私はWPでそのようなアプリを開発しました、私たちは開始時に作成されたたくさんのタスクを持っていました。

2
Dmitry Dyachkov

あなたは AsyncMVVM を試すことができます。

Page2.xaml:

<PhoneApplicationPage x:Class="Page2"
                      xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation">
    <ListView ItemsSource="{Binding Writings}" />
</PhoneApplicationPage>

Page2.xaml.cs:

public partial class Page2
{
    InitializeComponent();
    DataContext = new ViewModel2();
}

ViewModel2.cs:

public class ViewModel2: AsyncBindableBase
{
    public IEnumerable<Writing> Writings
    {
        get { return Property.Get(GetWritingsAsync); }
    }

    private async Task<IEnumerable<Writing>> GetWritingsAsync()
    {
        string jsonData = await JsonDataManager.GetJsonAsync("1");
        JObject obj = JObject.Parse(jsonData);
        JArray array = (JArray)obj["posts"];

        for (int i = 0; i < array.Count; i++)
        {
            Writing writing = new Writing();
            writing.content = JsonDataManager.JsonParse(array, i, "content");
            writing.date = JsonDataManager.JsonParse(array, i, "date");
            writing.image = JsonDataManager.JsonParse(array, i, "url");
            writing.summary = JsonDataManager.JsonParse(array, i, "excerpt");
            writing.title = JsonDataManager.JsonParse(array, i, "title");
            yield return writing;
        }
    }
}
1

任意のコンストラクタで時間のかかる操作を実行する簡単な方法は、アクションを作成して非同期で実行することです。

new Action( async () => await getWritings())();

このコードを実行しても、UIがブロックされることも、ゆるいスレッドが残ることもありません。また、UIを更新する必要がある場合(MVVMアプローチを使用していないと考えられる場合)、多くの人が示唆しているようにDispatcherを使用して更新できます。

0
Anup Sharma