4

xamarin と .net 5 async/awaits に基づいて Android メッセンジャー アプリを開発しています。

私のアプリには、無限ループで作成されたメッセージを処理するためのプロデューサー/コンシューマー パターンがあります。

たとえば、ReadTcpClientAsync プロデューサー:

async Task ReadTcpClientAsync(CancellationToken cancellationToken)
{
    cde.Signal();
    while (!cancellationToken.IsCancellationRequested)
    {
        byte[] buffer = await atc.ReadAsync(cancellationToken);
        // queue message...
    }
}

または、メッセージをデキューして WriteAsync を待機する SendStatementsAsync コンシューマ

private async Task SendStatementsAsync(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        var nextItem = await _outputStatements.Take();
        cancellationToken.ThrowIfCancellationRequested();
        // misc ...
        await atc.WriteAsync(call.Serialize());
    }
}

一部の消費者は、通話を待つだけです

 var update = await _inputUpdateStatements.Take();

この構成はテストではかなりうまく機能しますが、大きな間違いを犯したと思われる方法が 1 つあります。このメソッドは、3 つの pro/con while (true) ループを同時に開始して、クライアント バックエンド全体を実行することを目的としています。

ここにあります:

public async Task RunAsync()
{
   _isRunning = true;
   _progress.ProgressChanged += progress_ProgressChanged;
    await InitMTProto(_scheme).ConfigureAwait(false); // init smth...
    // various init stuf...     
    await atc.ConnectAsync().ConfigureAwait(false); // open connection async
    // IS IT WRONG?
    try
    {                                   
        await Task.WhenAny(SendStatementsAsync(_cts.Token),
                               ReadTcpClientAsync(_cts.Token),
                               ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
    }
    catch (OperationCanceledException oce)
    {   

    }
    catch (Exception ex)
    {

    }
}

今のところ Android のことは忘れて、UI コンテキストで任意の UI (WinForm、WPF など) の OnCreate メソッドを呼び出して RunAsync を呼び出すと考えてください。

protected async override void OnCreate(Bundle bundle)
{
    // start RA 
    await client.RunAsync()
    // never gets here - BAD, but nonblock UI thread - good
    Debug.WriteLine("nevar");
}

ご覧のとおり、問題があります。Task.WhenAny(...) から返されることはないため、RunAsync await 呼び出しの後は何もできません。そこでステータスチェックを実行する必要がありますが、この賛否両論メソッドを開始する必要があります。

if (!cde.Wait(15000))
{
    throw new TimeoutException("Init too long");
}

また、私のチェックも非同期で、魅力的に機能します:)

public async Task<TLCombinatorInstance> PerformRpcCall(string combinatorName, params object[] pars)
{
    // wait for init on cde ...
    // prepare call ...

    // Produce
    ProduceOutput(call);

    // wait for answer
    return await _inputRpcAnswersStatements.Take();
}

この無限ループを開始するには別のアプローチを使用する必要があると思いますが、すでに async Task メソッドをずっと持っているので、何をすべきかわかりません。何か助けてください。

4

1 に答える 1

2

わかりました、多くの読書(何も見つかりませんでした)と@svickのアドバイスの後、私はこのメソッドを別のTask.Runとして「待機」せずに呼び出すことにしました。Aso ThreadPool で実行することにしました。

私の最終的なコードは次のとおりです。

try
{                                   
    /*await Task.WhenAny(SendStatementsAsync(_cts.Token), 
           ReadTcpClientAsync(_cts.Token),
           ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);*/
    Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false);
    Task.Run(() => ReadTcpClientAsync(_cts.Token)).ConfigureAwait(false);
    Task.Run(() => ProcessUpdateAsync(_cts.Token, _progress)).ConfigureAwait(false);
    Trace.WriteLineIf(clientSwitch.TraceInfo, "Worker threads started", "[Client.RunAsync]");
}

すべてが期待どおりに正常に動作します.例外処理でどのような問題が発生するかはわかりませんが、それらが失われることはわかっています

もちろん、そのような呼び出しは警告を生成します

この呼び出しは待機されていないため、呼び出しが完了する前に現在のメソッドの実行が続行されます。呼び出しの結果に「await」演算子を適用することを検討してください。

この方法で簡単に抑制できます

// just save task into variable
var send = Task.Run(() => SendStatementsAsync(_cts.Token)).ConfigureAwait(false); 

また、誰かがより良い解決策を知っていれば、それを聞いて感謝します.

于 2013-08-12T07:18:26.490 に答える