簡體   English   中英

BlockingCollection(T).GetConsumingEnumerable()如何拋出OperationCanceledException?

[英]How can BlockingCollection(T).GetConsumingEnumerable() throw OperationCanceledException?

我正在使用BlockingCollection來實現任務調度程序,基本上:

public class DedicatedThreadScheduler : TaskScheduler, IDisposable
{
    readonly BlockingCollection<Task> m_taskQueue = new BlockingCollection<Task>();

    readonly Thread m_thread;


    public DedicatedThreadScheduler()
    {
        m_thread = new Thread(() =>
        {
            foreach (var task in m_taskQueue.GetConsumingEnumerable())
            {
                TryExecuteTask(task);
            }
            m_taskQueue.Dispose();
        });
        m_thread.Start();
    }

    public void Dispose()
    {
        m_taskQueue.CompleteAdding();
    }

    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return Thread.CurrentThread == m_thread && TryExecuteTask(task);
    }

    (...)
}

我只看過一次並且無法重現這一點,但是在foreach的某個時刻(在TryTakeWithNoTimeValidation中 )我得到了一個OperationCanceledException。 我不明白,因為我正在使用不采用CancellationToken的重載,並且文檔聲明它可能只拋出ObjectDisposedException。 這個例外是什么意思? 封鎖收集完成了嗎? 隊列中的任務被取消了?

更新:調用堆棧如下所示:

mscorlib.dll!System.Threading.SemaphoreSlim.WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, System.Threading.CancellationToken cancellationToken) + 0x36 bytes 
mscorlib.dll!System.Threading.SemaphoreSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) + 0x178 bytes   
System.dll!System.Collections.Concurrent.BlockingCollection<System.Threading.Tasks.Task>.TryTakeWithNoTimeValidation(out System.Threading.Tasks.Task item, int millisecondsTimeout, System.Threading.CancellationToken cancellationToken, System.Threading.CancellationTokenSource combinedTokenSource) Line 710 + 0x25 bytes   C#
System.dll!System.Collections.Concurrent.BlockingCollection<System.Threading.Tasks.Task>.GetConsumingEnumerable(System.Threading.CancellationToken cancellationToken) Line 1677 + 0x18 bytes    C#

這是一個老問題,但我會為將來發現它的人添加完整的答案。 Eugene提供的答案部分正確; 當時你必須使用Visual Studio進行調試,配置為打破已處理的框架異常。

但是,您在OperationCanceledException上打破的實際原因是BlockingCollection<T>.CompleteAdding()的代碼如下所示:

    public void CompleteAdding()
    {
        int num;
        this.CheckDisposed();
        if (this.IsAddingCompleted)
        {
            return;
        }
        SpinWait wait = new SpinWait();
    Label_0017:
        num = this.m_currentAdders;
        if ((num & -2147483648) != 0)
        {
            wait.Reset();
            while (this.m_currentAdders != -2147483648)
            {
                wait.SpinOnce();
            }
        }
        else if (Interlocked.CompareExchange(ref this.m_currentAdders, num | -2147483648, num) == num)
        {
            wait.Reset();
            while (this.m_currentAdders != -2147483648)
            {
                wait.SpinOnce();
            }
            if (this.Count == 0)
            {
                this.CancelWaitingConsumers();
            }
            this.CancelWaitingProducers();
        }
        else
        {
            wait.SpinOnce();
            goto Label_0017;
        }
    }

注意這些特定的行:

if (this.Count == 0)
{
    this.CancelWaitingConsumers();
}

調用此方法:

private void CancelWaitingConsumers()
{
    this.m_ConsumersCancellationTokenSource.Cancel();
}

因此,即使您未在代碼中明確使用CancellationToken ,如果在調用CompleteAdding()BlockingCollection為空,則底層框架代碼會拋出OperationCanceledException 它這樣做是為了通知GetConsumingEnumerable()方法退出。 該異常由框架代碼處理,如果您沒有將調試器配置為攔截它,您就不會注意到它。

你無法復制它的原因是因為你在Dispose()方法中調用了CompleteAdding() 因此,它在GC的一時興起被召喚。

我只能推測,但我認為你可能正在經歷由Stephen Toub在他的“Task.Wait和”內聯“博客文章和Jon Skeet 在這里描述的任務內聯方案。

你對TaskScheduler.TryExecuteTaskInline的實現是什么樣的? 要防止意外的任務內聯,請始終返回false

override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
    return false;
}

該異常偶爾發生在GetConsumingEnumerable枚舉器的MoveNext()方法中,但它是一個處理過的異常,所以通常你不應該看到它。

也許您已將調試器配置為中斷已處理的異常(在Visual Studio中,這些選項位於Debug / Exceptions菜單中),在這種情況下,調試器可能會在.NET框架函數內發生異常時收支平衡。

暫無
暫無

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

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