【问题标题】:Task.IsCompleted stays false even when CancelIsRequested即使 CancelIsRequested,Task.IsCompleted 也保持为假
【发布时间】:2013-06-13 03:59:47
【问题描述】:

在我的 LogManager 中,我有一个异步任务,它运行一个 while 循环来将日志信息写入文本文件(并将其添加到集合中)。当我尝试取消这个任务时,循环似乎停止了(我尝试了几个 Console.WriteLines),但是任务没有完成。

下面是这个类的主要方法:

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;
            }
        }
    }
}

以前的 Start/StopSaveLoop 方法:

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();
}

【问题讨论】:

  • 你可能在睡眠中遇到了死锁。不要混合同步和异步。
  • @SLaks 我已经添加了我以前的方法,因为它们停止工作,我在摆弄它们之前使用了这些方法。但是我从来没有看到“循环停止”的消息。任务状态保持“WaitingForActivation”
  • 请不要编写自己的记录器。这是您将遇到的许多心痛中的第一个。这个问题已经解决了。
  • @RyanGates 也许你是对的,但这是我现在遇到的最后一个问题,其他一切正常,所以我会愚蠢地抹去我所有的辛勤工作。另外我不认为这个问题与“记录器相关”,因为我只想知道为什么会发生这种情况,以防我在另一个上下文中需要类似的东西(我可能会)..
  • @RyanGates 我会鼓励每个人在遭受不同的“已构建”解决方案之后使用一个记录器...

标签: c# logging task-parallel-library


【解决方案1】:

我怀疑您可能会看到我描述的on my blogin a recent MSDN article 的死锁情况。如果 Application 启动和退出事件在 WPF UI 上下文中运行(我怀疑它们是),那么 SaveLoop 将尝试在 UI 线程上完成其 Task(它不能,因为 @987654326 @事件正在阻塞等待Task完成的UI线程。

您可以忽略这个问题(所有文件都已正确关闭,但缓冲的数据可能会丢失)。或者您可以调整您的解决方案以支持完全关闭。

一个调整选项是在后台线程上运行日志写入器(或者像以前一样使用Task.Run 来包装SaveLoop,或者对SaveLoop 中的每个await 使用ConfigureAwait(false))。这种方法的问题在于,如果集合是数据绑定到 UI 元素,它可能无法很好地与 AddLogToCollection 配合使用。

【讨论】:

  • 我尝试了 ConfigureAwait 和 Task.Run,​​两者都运行良好!谢谢你:) 但是我似乎得到了这个异常:“在 mscorlib.dll 中发生了'System.Threading.Tasks.TaskCanceledException' 类型的第一次机会异常”。我知道这可能没什么不好,但有没有办法抓住这个?我尝试在 SaveLoop 中尝试 catch,但在我捕捉到它之前它仍然被抛出
  • “第一次机会”异常在抛出时被调试器拦截(在您的代码捕获它之前)。您可以继续您的应用程序(允许您的代码捕获它)或告诉调试器不要拦截它们(调试 -> 异常 -> 取消选中所有抛出的框)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-05
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 2021-05-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多