簡體   English   中英

如何捕獲/觀察從Task拋出的未處理異常

[英]How to catch/observe an unhandled exception thrown from a Task

我正在嘗試在我的應用程序中記錄/報告所有未處理的異常(錯誤報告解決方案)。 我遇到了一個總是未處理的情景。 我想知道如何以未處理的方式捕獲此錯誤。 請注意,我今天早上做了很多研究並嘗試了很多東西..是的,我已經看過這個這個以及更多。 我只是在尋找一種通用的解決方案來記錄未處理的異常。

我在控制台測試應用程序主要方法中有以下代碼:

Task.Factory.StartNew(TryExecute);

要么

Task.Run((Action)TryExecute);

以及以下方法:

private static void TryExecute() {
   throw new Exception("I'm never caught");
}

我已經嘗試在我的應用程序中連接到以下內容,但它們從未被調用過。

AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException

在我最初發現此錯誤的Wpf應用程序中,我也連接到這些事件,但它從未被調用過。

Dispatcher.UnhandledException
Application.Current.DispatcherUnhandledException
System.Windows.Forms.Application.ThreadException

唯一被稱為的處理程序是:

AppDomain.CurrentDomain.FirstChanceException

但這不是一個有效的解決方案,因為我只想報告未捕獲的異常(並非每個異常,因為在執行/解析任何catch塊之前調用FirstChanceException。

如上所述, TaskScheduler.UnobservedTaskException事件應該為您提供所需的內容。 是什么讓你覺得它沒有被解雇?

在特定情況下,異常會被任務捕獲,然后重新拋出, 但不會立即重新拋出。 任務的異常會以多種方式重新拋出(在我的腦海中,可能還有更多)。

  1. 當您嘗試訪問結果時( Task.Result
  2. 在任務上調用Wait()Task.WaitOne()Task.WaitAll()或其他相關的Wait方法。
  3. 當您嘗試在不明確查看或處理異常的情況下處置Task

如果您執行上述任何操作,則會在運行代碼的任何線程上重新拋出異常, 並且由於您將觀察異常, 因此不會調用該事件 如果你沒有try {} catch {}的代碼,你將觸發AppDomain.CurrentDomain.UnhandledException ,這聽起來像是可能發生的事情。

重新拋出異常的另一種方式是:

  • 當您不執行上述任何操作時,任務仍然會將異常視為未觀察到,並且任務正在完成。 它是最后的努力,讓你知道有一個你沒有看到的例外。

如果是這種情況,並且由於終結器是非確定性的,您是否正在等待GC發生,以便將具有未觀察到的異常的那些任務放入終結器隊列中,然后再等待它們最終確定?

編輯: 這篇文章談到了這一點。 本文將討論事件存在的原因,這可能會讓您深入了解如何正確使用事件。

我使用MSDN中的LimitedTaskScheduler捕獲所有異常,包括使用TPL的其他線程:


public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
    /// Whether the current thread is processing work items.
    [ThreadStatic]
    private static bool currentThreadIsProcessingItems;

    /// The list of tasks to be executed.
    private readonly LinkedList tasks = new LinkedList(); // protected by lock(tasks)

    private readonly ILogger logger;

    /// The maximum concurrency level allowed by this scheduler.
    private readonly int maxDegreeOfParallelism;

    /// Whether the scheduler is currently processing work items.
    private int delegatesQueuedOrRunning; // protected by lock(tasks)

    public LimitedConcurrencyLevelTaskScheduler(ILogger logger) : this(logger, Environment.ProcessorCount)
    {
    }

    public LimitedConcurrencyLevelTaskScheduler(ILogger logger, int maxDegreeOfParallelism)
    {
        this.logger = logger;

        if (maxDegreeOfParallelism Gets the maximum concurrency level supported by this scheduler.
    public override sealed int MaximumConcurrencyLevel
    {
        get { return maxDegreeOfParallelism; }
    }

    /// Queues a task to the scheduler.
    /// The task to be queued.
    protected sealed override void QueueTask(Task task)
    {
        // Add the task to the list of tasks to be processed.  If there aren't enough
        // delegates currently queued or running to process tasks, schedule another.
        lock (tasks)
        {
            tasks.AddLast(task);

            if (delegatesQueuedOrRunning >= maxDegreeOfParallelism)
            {
                return;
            }

            ++delegatesQueuedOrRunning;

            NotifyThreadPoolOfPendingWork();
        }
    }

    /// Attempts to execute the specified task on the current thread.
    /// The task to be executed.
    /// 
    /// Whether the task could be executed on the current thread.
    protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        // If this thread isn't already processing a task, we don't support inlining
        if (!currentThreadIsProcessingItems)
        {
            return false;
        }

        // If the task was previously queued, remove it from the queue
        if (taskWasPreviouslyQueued)
        {
            TryDequeue(task);
        }

        // Try to run the task.
        return TryExecuteTask(task);
    }

    /// Attempts to remove a previously scheduled task from the scheduler.
    /// The task to be removed.
    /// Whether the task could be found and removed.
    protected sealed override bool TryDequeue(Task task)
    {
        lock (tasks)
        {
            return tasks.Remove(task);
        }
    }

    /// Gets an enumerable of the tasks currently scheduled on this scheduler.
    /// An enumerable of the tasks currently scheduled.
    protected sealed override IEnumerable GetScheduledTasks()
    {
        var lockTaken = false;

        try
        {
            Monitor.TryEnter(tasks, ref lockTaken);

            if (lockTaken)
            {
                return tasks.ToArray();
            }
            else
            {
                throw new NotSupportedException();
            }
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(tasks);
            }
        }
    }

    protected virtual void OnTaskFault(AggregateException exception)
    {
        logger.Error(exception);
    }

    /// 
    /// Informs the ThreadPool that there's work to be executed for this scheduler.
    /// 
    private void NotifyThreadPoolOfPendingWork()
    {
        ThreadPool.UnsafeQueueUserWorkItem(ExcuteTask, null);
    }

    private void ExcuteTask(object state)
    {
        // Note that the current thread is now processing work items.
        // This is necessary to enable inlining of tasks into this thread.
        currentThreadIsProcessingItems = true;

        try
        {
            // Process all available items in the queue.
            while (true)
            {
                Task item;
                lock (tasks)
                {
                    // When there are no more items to be processed,
                    // note that we're done processing, and get out.
                    if (tasks.Count == 0)
                    {
                        --delegatesQueuedOrRunning;
                        break;
                    }

                    // Get the next item from the queue
                    item = tasks.First.Value;
                    tasks.RemoveFirst();
                }

                // Execute the task we pulled out of the queue
                TryExecuteTask(item);

                if (!item.IsFaulted)
                {
                    continue;
                }

                OnTaskFault(item.Exception);
            }
        }
        finally
        {
            // We're done processing items on the current thread
            currentThreadIsProcessingItems = false;
        }
    }
}

而不是使用Reflection將TaskScheduler的“注冊”作為默認值:


public static class TaskLogging
{
    private const BindingFlags StaticBinding = BindingFlags.Static | BindingFlags.NonPublic;

    public static void SetScheduler(TaskScheduler taskScheduler)
    {
        var field = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", StaticBinding);
        field.SetValue(null, taskScheduler);

        SetOnTaskFactory(new TaskFactory(taskScheduler));
    }

    private static void SetOnTaskFactory(TaskFactory taskFactory)
    {
        var field = typeof(Task).GetField("s_factory", StaticBinding);
        field.SetValue(null, taskFactory);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM