Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/264.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# BlockingCollection(T).GetConsumingEnumerable()如何抛出OperationCanceledException?_C#_Task Parallel Library - Fatal编程技术网

C# BlockingCollection(T).GetConsumingEnumerable()如何抛出OperationCanceledException?

C# BlockingCollection(T).GetConsumingEnumerable()如何抛出OperationCanceledException?,c#,task-parallel-library,C#,Task Parallel Library,我使用BlockingCollection来实现任务调度器,基本上: public class DedicatedThreadScheduler : TaskScheduler, IDisposable { readonly BlockingCollection<Task> m_taskQueue = new BlockingCollection<Task>(); readonly Thread m_thread; public Dedica

我使用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);
    }

    (...)
}
public类专用线程调度器:任务调度器,IDisposable
{
只读BlockingCollection m_taskQueue=新建BlockingCollection();
只读线程m_线程;
公共专用线程调度程序()
{
m_线程=新线程(()=>
{
foreach(m_taskQueue.GetConsumingEnumerable()中的var任务)
{
TryExecuteTask(任务);
}
m_taskQueue.Dispose();
});
m_thread.Start();
}
公共空间处置()
{
m_taskQueue.CompleteAdding();
}
受保护的覆盖bool TryExecuteTaskInline(任务任务,bool任务先前排队)
{
return Thread.CurrentThread==m_Thread&&TryExecuteTask(任务);
}
(...)
}
我只见过一次,无法重现,但在foreach(in)上的某个点上,我得到了一个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#
    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;
        }
    }
mscorlib.dll!System.Threading.SemaphoreSlim.WaitUntillCountertimeout(int毫秒计时,uint开始时间,System.Threading.CancellationToken CancellationToken)+0x36字节
mscorlib.dll!System.Threading.SemaphoreSlim.Wait(int毫秒计时,System.Threading.CancellationToken CancellationToken)+0x178字节
System.dll!System.Collections.Concurrent.BlockingCollection.TryTakeWithNotTimeValidation(out System.Threading.Tasks.Task项,int毫秒计时,System.Threading.CancellationToken CancellationToken,System.Threading.CancellationTokenSource combinedTokenSource)行710+0x25字节C#
System.dll!System.Collections.Concurrent.BlockingCollection.GetConsumingEnumerable(System.Threading.CancellationToken CancellationToken)行1677+0x18字节C#

我只能猜测,但我认为您可能正在体验Stephen Toub在其博客文章和Jon Skeet所描述的任务内联场景

TaskScheduler.TryExecuteTaskInline
的实现是什么样子的?要防止意外的任务内联,请始终返回
false

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

该异常偶尔会发生在GetConsumingEnumerable枚举数的MoveNext()方法中,但它是一个已处理的异常,所以通常不应该看到它


也许您已将调试器配置为在处理异常时中断(在Visual Studio中,这些选项位于“调试/异常”菜单中),在这种情况下,调试器可能会在.NET framework函数中发生异常时实现收支平衡。

这是一个老问题,但我将为将来找到它的任何人添加完整答案。尤金提供的答案部分是正确的;当时,您必须已使用配置为在处理框架异常时中断的Visual Studio进行调试

但是,您中断
操作取消异常的实际原因是
BlockingCollection.completedadding()
的代码如下所示:

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#
    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
。它这样做是为了向退出的
getConsumineumerable()
方法发出信号。异常由框架代码处理,如果没有将调试器配置为拦截它,您不会注意到它


无法复制它的原因是您在
Dispose()
方法中调用了
CompleteAdding()
。因此,它是在GC的突发奇想下被调用的。

您检查过堆栈跟踪了吗?异常可能发生在
BlockingCollection
调用的方法中。是的,正如我在描述中添加的,它发生在TryTakeWIthNoTimeValidation中。就我所知,通过查看源代码,如果向它传递了CancellationToken,它应该只抛出OperationCanceledException,但我使用的重载不会…用调用堆栈更新问题。Dispose()方法和foreach循环之间没有互锁。通常可以正常工作,而不是因为某些原因过早调用Dispose()。@HansPassant这应该可以,因为阻塞集合的Dispose()必须在同一线程上的foreach之后调用。该类的Dispose()方法仅调用CompleteAdding(),以防止新任务排队。在问题中添加了TryExecuteTaskInline的实现。我将尝试在调用该方法时查看是否发生这种情况,感谢您的洞察力。这不是全部。我会把真正的原因写在一个单独的答案里。这是一个很好的答案!我想下一个明显的问题是我是否需要处理这个异常,如果需要,如何处理?这很可能发生在生产商多而消费者少的时候。不,你不需要处理它。它只是用作内部信号机制。只有将调试器配置为拦截它时,您才会意识到它。