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();
}
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 theSaveLoop
would try to finish itsTask
on the UI thread (which it cannot, because theExit
event is blocking the UI thread waiting for theTask
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 wrapSaveLoop
like you were doing before, or usingConfigureAwait(false)
for everyawait
inSaveLoop
). The problem with this approach is that it may not play well withAddLogToCollection
if the collections are data-bound to UI elements.