Search code examples
c#task-parallel-library

Passing CancellationTokenSource to awaited Task


Read a lot of articles an watch many videos on Task Cancelation. But I still can't quite follow if it's possible to pass CancellationTokenSource instead of CancellationTokenSource providing that the condition required for a cancelation is being obtained inside of Task awaited?

protected CancellationTokenSource source = new CancellationTokenSource();
protected CancellationToken cancel_token => source.Token;


protected async Task FinishAsync(string message)
{
    TimeSpan time = DateTime.Now - startTime;

    if (GlobalVars.logger != null)
        await GlobalVars.logger.LogDictionaryAsync("Elapsed " + Math.Round((time.TotalMilliseconds / 1000), 3).ToString() + "s " + "Token " + token + " " + user + " \n" + message, LogLevel.Reply, source );
    cancel_token.ThrowIfCancellationRequested();
}


public async Task LogDictionaryAsync(string s, LogLevel logLevel = LogLevel.Info, string jobId = "", CancellationTokenSource source = null)
{
    if (string.IsNullOrWhiteSpace(s))
        return;

    OpenStream();
    await semaphore.WaitAsync();

    if (string.IsNullOrWhiteSpace(s))
        return;
    try
    {
        byte[] bytes = Encoding.UTF8.GetBytes(LocalDate.ToString("dd/MM/yyyy H:mm:ss.FFF") + " " +
                                                (!string.IsNullOrWhiteSpace(jobId) ? jobId : Thread.CurrentThread.ManagedThreadId.ToString().PadLeft(5, ' ')) + " " +
                                                logLevel.ToString() + " " +
                                                (s.Length <= MESSAGE_MAX_SIZE ? s : s.Substring(0, MESSAGE_MAX_SIZE) + "...(truncated)") +
                                                "\n");
        if (bytes.Length == 0)
        {
            source?.Cancel();
        }
        await fstream.WriteAsync(bytes, 0, bytes.Length);
        fstream.Flush();
    }
    catch (AggregateException e)
    {
        throw e;

    }
    catch (Exception e)
    {
        Console.Write(e.Message);

    }
    finally
    {
        source?.Dispose();
        semaphore.Release();
        GC.Collect();
    }

};

I call cancel_token.ThrowIfCancellationRequested() basing on the canceletion got in Task awaited.

Thank you Don't know exactly what is right


Solution

  • Cancelling the token source makes no sense if you already know you need to bail out. And you have a few issues with your code.

    Firsty, you can pass the token without passing the whole source.

    public async Task LogDictionaryAsync(string s, LogLevel logLevel = LogLevel.Info, string jobId = "",
        CancellationToken token = default)
    

    and

    await GlobalVars.logger.LogDictionaryAsync(
        $"Elapsed { Math.Round((time.TotalMilliseconds / 1000), 3)}s Token {token} {user} \n{message}",
        LogLevel.Reply, source.Token);
    

    You need to pass the token in various places inside that function, otherwise it won't work, as cancellation is cooperative.

    Note that you should never acquire a semaphore and then dump it without releasing it.

    if (string.IsNullOrWhiteSpace(s))
        return;
    
    await semaphore.WaitAsync(token);
    try
    {
        byte[] bytes = Encoding.UTF8.GetBytes(
            $"{LocalDate:dd/MM/yyyy H:mm:ss.FFF} {
                 (!string.IsNullOrWhiteSpace(jobId) ? jobId : Thread.CurrentThread.ManagedThreadId.ToString()), 5
              } {logLevel} {
                 (s.Length <= MESSAGE_MAX_SIZE ? s : s.Substring(0, MESSAGE_MAX_SIZE) + "...(truncated)")
              }\n");
        if (bytes.Length == 0)
        {
            return;
        }
        await fstream.WriteAsync(bytes, 0, bytes.Length, token);
        await fstream.FlushAsync(token);
    }
    catch (Exception e)
    {
        Console.Write(e.Message);
    }
    finally
    {
        semaphore.Release();
    }
    

    Note also the use of string formatting to make this more concise.