-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Use Channel Instead of BufferBlock #5123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
f61033e
416da19
72beecb
e688d05
77ec834
32f3a01
b6b39c8
8fc6a56
335b724
8a45c1d
9b3a5e6
325f8dd
b853e84
158ab07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,8 @@ | |
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Threading; | ||
| using System.Threading.Channels; | ||
| using System.Threading.Tasks; | ||
| using System.Threading.Tasks.Dataflow; | ||
| using Microsoft.ML; | ||
| using Microsoft.ML.CommandLine; | ||
| using Microsoft.ML.Data; | ||
|
|
@@ -486,8 +486,8 @@ private static readonly FuncInstanceMethodInfo1<Cursor, int, Delegate> _createGe | |
| private int _liveCount; | ||
| private bool _doneConsuming; | ||
|
|
||
| private readonly BufferBlock<int> _toProduce; | ||
| private readonly BufferBlock<int> _toConsume; | ||
| private readonly Channel<int> _toProduceChannel; | ||
| private readonly Channel<int> _toConsumeChannel; | ||
| private readonly Task _producerTask; | ||
| private Exception _producerTaskException; | ||
|
|
||
|
|
@@ -541,13 +541,13 @@ public Cursor(IChannelProvider provider, int poolRows, DataViewRowCursor input, | |
| _liveCount = 1; | ||
|
|
||
| // Set up the producer worker. | ||
| _toConsume = new BufferBlock<int>(); | ||
| _toProduce = new BufferBlock<int>(); | ||
| _toConsumeChannel = Channel.CreateUnbounded<int>(new UnboundedChannelOptions { SingleWriter = true }); | ||
| _toProduceChannel = Channel.CreateUnbounded<int>(new UnboundedChannelOptions { SingleWriter = true }); | ||
| // First request the pool - 1 + block size rows, to get us going. | ||
| PostAssert(_toProduce, _poolRows - 1 + _blockSize); | ||
| PostAssert(_toProduceChannel, _poolRows - 1 + _blockSize); | ||
| // Queue up the remaining capacity. | ||
| for (int i = 1; i < _bufferDepth; ++i) | ||
| PostAssert(_toProduce, _blockSize); | ||
| PostAssert(_toProduceChannel, _blockSize); | ||
|
|
||
| _producerTask = ProduceAsync(); | ||
| } | ||
|
|
@@ -557,30 +557,13 @@ protected override void Dispose(bool disposing) | |
| if (_disposed) | ||
| return; | ||
|
|
||
| if (disposing) | ||
| { | ||
| _toProduce.Complete(); | ||
| _producerTask.Wait(); | ||
|
|
||
| // Complete the consumer after the producerTask has finished, since producerTask could | ||
| // have posted more items to _toConsume. | ||
| _toConsume.Complete(); | ||
|
|
||
| // Drain both BufferBlocks - this prevents what appears to be memory leaks when using the VS Debugger | ||
| // because if a BufferBlock still contains items, its underlying Tasks are not getting completed. | ||
| // See https://github.com/dotnet/corefx/issues/30582 for the VS Debugger issue. | ||
| // See also https://github.com/dotnet/machinelearning/issues/4399 | ||
| _toProduce.TryReceiveAll(out _); | ||
| _toConsume.TryReceiveAll(out _); | ||
| } | ||
|
|
||
| _disposed = true; | ||
| base.Dispose(disposing); | ||
| } | ||
|
|
||
| public static void PostAssert<T>(ITargetBlock<T> target, T item) | ||
| public static void PostAssert<T>(Channel<T> target, T item) | ||
| { | ||
| bool retval = target.Post(item); | ||
| bool retval = target.Writer.TryWrite(item); | ||
| Contracts.Assert(retval); | ||
| } | ||
|
|
||
|
|
@@ -594,12 +577,13 @@ private async Task ProduceAsync() | |
| try | ||
| { | ||
| int circularIndex = 0; | ||
| while (await _toProduce.OutputAvailableAsync().ConfigureAwait(false)) | ||
| while (await _toProduceChannel.Reader.WaitToReadAsync().ConfigureAwait(false)) | ||
|
jwood803 marked this conversation as resolved.
|
||
| { | ||
| int requested; | ||
| if (!_toProduce.TryReceive(out requested)) | ||
| if (!_toProduceChannel.Reader.TryRead(out requested)) | ||
| { | ||
| // OutputAvailableAsync returned true, but TryReceive returned false - | ||
| // The producer Channel's Reader.WaitToReadAsync returned true, | ||
| // but the Reader's TryRead returned false - | ||
| // so loop back around and try again. | ||
| continue; | ||
| } | ||
|
|
@@ -618,14 +602,14 @@ private async Task ProduceAsync() | |
| if (circularIndex == _pipeIndices.Length) | ||
| circularIndex = 0; | ||
| } | ||
| PostAssert(_toConsume, numRows); | ||
| PostAssert(_toConsumeChannel, numRows); | ||
| if (numRows < requested) | ||
| { | ||
| // We've reached the end of the cursor. Send the sentinel, then exit. | ||
| // This assumes that the receiver will receive things in Post order | ||
| // (so that the sentinel is received, after the last Post). | ||
| if (numRows > 0) | ||
| PostAssert(_toConsume, 0); | ||
| PostAssert(_toConsumeChannel, 0); | ||
| return; | ||
| } | ||
| } | ||
|
|
@@ -634,7 +618,7 @@ private async Task ProduceAsync() | |
| { | ||
| _producerTaskException = ex; | ||
| // Send the sentinel in this case as well, the field will be checked. | ||
| PostAssert(_toConsume, 0); | ||
| PostAssert(_toConsumeChannel, 0); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -651,26 +635,29 @@ protected override bool MoveNextCore() | |
| { | ||
| // We should let the producer know it can give us more stuff. | ||
| // It is possible for int values to be sent beyond the | ||
| // end of the sentinel, but we suppose this is irrelevant. | ||
| PostAssert(_toProduce, _deadCount); | ||
| // end of the Channel, but we suppose this is irrelevant. | ||
| PostAssert(_toProduceChannel, _deadCount); | ||
| _deadCount = 0; | ||
| } | ||
|
|
||
| while (_liveCount < _poolRows && !_doneConsuming) | ||
| { | ||
| // We are under capacity. Try to get some more. | ||
| int got = _toConsume.Receive(); | ||
| if (got == 0) | ||
| var hasReadItem = _toConsumeChannel.Reader.TryRead(out int got); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously It might make sense to keep the previous behavior and block the thread until |
||
| if (hasReadItem) | ||
| { | ||
| // We've reached the end sentinel. There's no reason | ||
| // to attempt further communication with the producer. | ||
| // Check whether something horrible happened. | ||
| if (_producerTaskException != null) | ||
| throw Ch.Except(_producerTaskException, "Shuffle input cursor reader failed with an exception"); | ||
| _doneConsuming = true; | ||
| break; | ||
| if (got == 0) | ||
| { | ||
| // We've reached the end of the Channel. There's no reason | ||
| // to attempt further communication with the producer. | ||
| // Check whether something horrible happened. | ||
| if (_producerTaskException != null) | ||
| throw Ch.Except(_producerTaskException, "Shuffle input cursor reader failed with an exception"); | ||
| _doneConsuming = true; | ||
| break; | ||
| } | ||
| _liveCount += got; | ||
| } | ||
| _liveCount += got; | ||
| } | ||
| if (_liveCount == 0) | ||
| return false; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.