Task.IsCompleted stays false even when CancelIsReq

2019-09-11 04:57发布

问题:

In my LogManager, I have an async task that runs a while loop to write log information to a text file (and add it to a collection). When I try to cancel this task, the loop seems to stop (I tried with several Console.WriteLines), but the task however doesn't complete.

Here are the main methods of this class:

public static DateTime StartTime;
private static CancellationTokenSource SaveLoopToken;
private static Task SaveLoopTask;

public static void Start()
{
    LogMessage startMessage = new LogMessage("Logging Started");
    startMessage.SetCaller("StartLogging", "LogManager");
    StartTime = startMessage.Time;
    LogQueue.Post(startMessage);
    StartSaveLoop();
}

public static void Stop()
{
    LogMessage stopMessage = new LogMessage("Logging Stopped");
    stopMessage.SetCaller("StopLogging", "LogManager");
    LogQueue.Post(stopMessage);
    StopSaveLoop();
}

private static void StartSaveLoop()
{
    SaveLoopToken = new CancellationTokenSource();
    SaveLoopTask = SaveLoop(SaveLoopToken);
    Console.WriteLine("Loop started!");
}

private static void StopSaveLoop()
{
    Console.WriteLine("Stop requested");

    SaveLoopToken.Cancel();

    while (!SaveLoopTask.IsCompleted)
    {
        Thread.Sleep(100);
    }

    Console.WriteLine("Loop stopped!");
}

private static void AddLogToCollection(LogMessage logMessage)
{
    // Add to MessageList
    MessageList.Add(logMessage);

    // Add to CurrentMessageList
    CurrentMessageList.Add(logMessage);

    // Add to FilteredMessageList
    if (logMessage.Level >= FilterLevel)
    {
        FilteredMessageList.Add(logMessage);
    }
}

private static async Task SaveLoop(CancellationTokenSource cancel)
{
    string logPath = "logs\\";
    string logFile = StartTime.ToString("yyyy-MM-dd_HH-mm-ss") + ".log";
    string fullPath = Path.Combine(logPath, logFile);

    if (!Directory.Exists(logPath))
        Directory.CreateDirectory(logPath);

    using (FileStream fileStream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.Read, 8192, useAsync: true))
    {
        using (StreamWriter writer = new StreamWriter(fileStream))
        {
            while (true)
            {
                LogMessage logMessage = await LogQueue.ReceiveAsync(cancel.Token);

                AddLogToCollection(logMessage);

                await writer.WriteAsync(String.Format("({0}) ", logMessage.LogID));
                await writer.WriteAsync(String.Format("[{0}][{1}] ", logMessage.Time.ToString("HH:mm:ss:fff"), logMessage.Level.ToString()));
                await writer.WriteAsync(logMessage.Message);
                await writer.WriteLineAsync(String.Format(" ({0} - {1})", logMessage.Method, logMessage.Location));

                if (cancel.IsCancellationRequested)
                    break;
            }
        }
    }
}

Previous Start/StopSaveLoop methods:

private static async void StartSaveLoop()
{
    SaveLoopToken = new CancellationTokenSource();
    Console.WriteLine("Loop started!");

    await SaveLoop(SaveLoopToken);
    Console.WriteLine("Loop stoped!");
}

private static void StopSaveLoop()
{
    Console.WriteLine("Stop requested");
    SaveLoopToken.Cancel();
}

回答1:

I suspect you may be seeing a deadlock situation that I describe on my blog and in a recent MSDN article. If the Application start and exit events are run within the WPF UI context (which I suspect they are), then the SaveLoop would try to finish its Task on the UI thread (which it cannot, because the Exit event is blocking the UI thread waiting for the Task to complete).

You could just ignore the problem (all files are properly closed, but buffered data may be lost). Or you could tweak your solution to support a clean shutdown.

One tweaking option is to run the log writer on a background thread (either using Task.Run to wrap SaveLoop like you were doing before, or using ConfigureAwait(false) for every await in SaveLoop). The problem with this approach is that it may not play well with AddLogToCollection if the collections are data-bound to UI elements.