web-dev-qa-db-ja.com

CancellationTokenを使用してStreamReader.ReadLineAsyncをキャンセルできますか?

CancellationTokenSourceCancel()メソッドを呼び出して、次の内容の非同期メソッドをキャンセルすると、最終的に停止します。ただし、行Console.WriteLine(await reader.ReadLineAsync());の完了にはかなりの時間がかかるため、メソッドをさらに作成するために、CancellationTokenをReadLineAsync()にも渡そうとしました(空の文字列を返すことを期待しています)。 Cancel()呼び出しに応答します。ただし、CancellationTokenReadLineAsync()に渡すことができませんでした。

Console.WriteLine()またはStreamreader.ReadLineAsync()の呼び出しをキャンセルできますか?キャンセルする場合はどうすればよいですか?

ReadLineAsync()CancellationTokenを受け入れないのはなぜですか?メソッドがキャンセルされた後も完了している場合でも、AsyncメソッドにオプションのCancellationTokenパラメーターを指定することをお勧めします。

_StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
    if (ct.IsCancellationRequested){
        ct.ThrowIfCancellationRequested();
        break;
    }
    else
    {
        Console.WriteLine(await reader.ReadLineAsync());
    }
}
_

更新以下のコメントに記載されているように、Console.WriteLine()呼び出しだけでも、1行あたり40.000文字の入力文字列の形式が不適切なため、すでに数秒かかっていました。これを分解すると応答時間の問題は解決しますが、何らかの理由で1行に40.000文字を書き込むことが意図されている場合(たとえば、文字列全体をにダンプする場合)、この長時間実行されるステートメントをキャンセルする方法に関する提案や回避策に関心があります。ファイル)。

18
H W

キャンセルできない限り、操作をキャンセルすることはできません。 WithCancellation拡張メソッドを使用して、コードフローをキャンセルされたかのように動作させることができますが、基になるコードは引き続き実行されます。

public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
    return task.IsCompleted // fast-path optimization
        ? task
        : task.ContinueWith(
            completedTask => completedTask.GetAwaiter().GetResult(),
            cancellationToken,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);
}

使用法:

await task.WithCancellation(cancellationToken);

Console.WriteLineをキャンセルすることはできず、キャンセルする必要はありません。妥当なサイズのstringがあれば、それは瞬時です。

ガイドラインについて:実装が実際にキャンセルをサポートしていない場合、トークンは混合メッセージを送信するため、トークンを受け入れるべきではありません。

コンソールに書き込むための巨大な文字列がある場合は、Console.WriteLineを使用しないでください。一度に1文字ずつ文字列を記述し、そのメソッドをキャンセル可能にすることができます。

public void DumpHugeString(string line, CancellationToken token)
{
    foreach (var character in line)
    {
        token.ThrowIfCancellationRequested();
        Console.Write(character);
    }

    Console.WriteLine();
}

さらに良い解決策は、単一の文字ではなくバッチで書き込むことです。 MoreLinqBatchを使用した実装は次のとおりです。

public void DumpHugeString(string line, CancellationToken token)
{
    foreach (var characterBatch in line.Batch(100))
    {
        token.ThrowIfCancellationRequested();
        Console.Write(characterBatch.ToArray());
    }

    Console.WriteLine();
}

したがって、結論として:

var reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
    DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token);
}
10
i3arnon

私はこれを一般化しました answer これに:

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
    using (cancellationToken.Register(action, useSynchronizationContext))
    {
        try
        {
            return await task;
        }
        catch (Exception ex)
        {

            if (cancellationToken.IsCancellationRequested)
            {
                // the Exception will be available as Exception.InnerException
                throw new OperationCanceledException(ex.Message, ex, cancellationToken);
            }

            // cancellation hasn't been requested, rethrow the original Exception
            throw;
        }
    }
}

これで、キャンセル可能な非同期メソッドでキャンセルトークンを使用できます。例:WebRequest.GetResponseAsync:

var request = (HttpWebRequest)WebRequest.Create(url);

using (var response = await request.GetResponseAsync())
{
    . . .
}

となります:

var request = (HttpWebRequest)WebRequest.Create(url);

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
    . . .
}

例を参照してください http://Pastebin.com/KauKE0rW

3
Marat Asadurian

私は無限の遅延を使用するのが好きです、コードはかなりきれいです。 waitingが完了すると、WhenAnyが返され、cancellationTokenがスローされます。それ以外の場合は、taskの結果が返されます。

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
        using (var delayCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
        {
            var waiting = Task.Delay(-1, delayCTS.Token);
            var doing = task;
            await Task.WhenAny(waiting, doing);
            delayCTS.Cancel();
            cancellationToken.ThrowIfCancellationRequested();
            return await doing;
        }
}
1
Nicolas Dorier