web-dev-qa-db-ja.com

Xamarinでawaitを使用する方法Androidアクティビティコールバック

タイトルは少し誤解を招くかもしれませんが、私の質問は、なぜそれがこの奇妙な方法で機能するのかについてです。

したがって、TextViewとListViewを持つレイアウトのアクティビティがあります。リストに表示するデータを準備する、長時間実行されているasyncメソッドがあります。したがって、初期コードは次のようになります。

    protected async override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.MyView);
        await SetupData();
    }

    private async Task SetupData(){
        Task.Run(async () => {
            var data = await new SlowDataLoader().LoadDataAsync();
            // For simplicity setting data on the adapter is omitted here
        });
    }

エラーなしで実行されるという意味で機能します。ただし、アクティビティは空白の画面として表示され、テキストビューでさえ特定の遅延後にのみレンダリングされます。したがって、タスクは実際には非同期で実行されていないようです。両方の「待機」呼び出しでConfigureAwait(false)を設定しても役に立ちませんでした。 SetupData()呼び出しをOnPostCreate、OnResume、およびOnPostResumeに移動しても効果はありません。 TextViewをすぐに表示し、後でデータが到着したときにリストをレンダリングする唯一のことは、次のとおりです。

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.MyView);
        new Handler().PostDelayed(async ()=>{
            await SetupData();
        }, 100);
    }

だから問題は、なぜそうしないのかということです

await SetupData().ConfigureAwait(false); 

フローのブロックを解除しますか?たとえ(これによると http://www.wintellect.com/devcenter/paulballard/tasks-are-still-not -threads-and-async-is-not-parallel )SetupDataは、ここでは別のスレッドとして実行できるはずですか?

p.s.アダプターにデータを設定するコードを削除しても、この動作には影響しません。画面がレンダリングされるまでに遅延があります。したがって、ここではそのコードを示していません。

9
Dennis K

UIルーパー内の待機中により、SetupDataメソッドの実行中に、スレッドでのそれ以上のコード実行をブロックします。

ノンブロッキングの例:

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        SetContentView(Resource.Layout.Main);
        Task.Run(() => SetupData());
        Console.WriteLine("UI Thread / Message Looper is not blocked");
    }

    void SetupData()
    {
        Task.Run(async () =>
        {
            Console.WriteLine($"Are we on the UI thread? {Looper.MainLooper.Thread == Looper.MyLooper()?.Thread}");
            // Simulate a long running task
            await Task.Delay(TimeSpan.FromSeconds(10));
            Console.WriteLine("Done fetching/calculating data");
            RunOnUiThread(() =>
            {
                // Update the data fetched/calculated on the UI thread;
                Console.WriteLine($"Are we on the UI thread? {Looper.MainLooper.Thread == Looper.MyLooper().Thread}");
            });
        }).Wait();
        Console.WriteLine("Done w/ SetupData");
    }

出力:

UI Thread / Message Looper is not blocked
Are we on the UI thread? False
Done fetching/calculating data
Are we on the UI thread? True
Done w/ SetupData
16
SushiHangover

@SushiHangoverからの回答を補足するために、@ SushiHangoverによって提案されたものに加えて、実際のバグを指摘し、考えられる解決策をリストするための独自の回答を追加します。

このページの一番下にある例を検討してください https://msdn.Microsoft.com/en-us/library/hh156528.aspx

元のコード(および私が試した他のすべてのバリアント)の本当の問題は、SetupDataが非同期メソッドとして宣言されていても、実際には同期として実行されていたということでした。したがって、OnCreateが同期メソッドを待機しているとき、それはブロックしていました(上記の例でデモしたものとまったく同じです)。この問題は、いくつかの方法で修正できます。まず、SushiHangoverが提案したように、このメソッドを待たずに、同期しているので、そのように呼び出します(asyncキーワードを削除して、voidを返すこともできます)。

状況によってはより適切な別のアプローチは、そのメソッド内で作成されたタスクを待つことです。

private async Task SetupData(){
    await Task.Run(async () => {
        var data = await new SlowDataLoader().LoadDataAsync();
        // For simplicity setting data on the adapter is omitted here
    });
}

または、タスクを返すことにより、非同期メソッドの要件に準拠するようにこのメソッドを変更します。

private Task SetupData(){
    return Task.Run(async () => {
        var data = await new SlowDataLoader().LoadDataAsync();
        // For simplicity setting data on the adapter is omitted here
    });
}

これらの変更は両方とも、OnCreateでの待機が期待どおりに機能することを可能にします-データがまだロードされている間にOnCreateメソッドが終了します。

1
Dennis K

UIスレッドで実行されていないため、これはより明確なビューを取得するのに役立ちます Android UiThread(UIスレッド)

0
Mike