我用TPL Dataflow创建了一个小型管道。它由TransformBlock
链接到ActionBlock
组成。设置如下:
var transformBlock = new TransformBlock<int, string>(async num=>
{
// Do stuff.
return num.ToString();
}, new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = Environment.ProcessorCount,
});
var actionBlock = new ActionBlock<string>(text=>
{
Console.WriteLine(text);
});
transformBlock.LinkTo(actionBlock, new DataflowLinkOptions
{
PropagateCompletion = true
});
我想改变转换块实例的MaxDegreeOfParallelism
。我没有办法这样做,因为没有公共财产允许我改变它。有办法吗?
答案 0 :(得分:2)
根据MSDN:
数据流阻止捕获构造选项的状态。对提供的ExecutionDataflowBlockOptions实例的后续更改不应影响数据流块的行为。
所以,回答你的问题,
我想改变转换块实例的
MaxDegreeOfParallelism
。
它不适合现有的块来更改它的选项。您要么应该创建新管道,要么创建新管道,或者从头开始调整此设置以获得最大利润。
答案 1 :(得分:0)
我试图通过使用可交换的TransformBlock
(夹在两个BufferBlock
之间)来解决此问题。每次更改MaxDegreeOfParallelism
时,都会分离旧的TransformBlock
,标记为已完成,等待其完成,然后在其位置附加新的TransformBlock
。该解决方案有两个缺点:
在替换中间块的间歇阶段,并行度逐渐降低到零。
中间块的BoundedCapacity
必须设置为MaxDegreeOfParallelism
,这可能会影响非常精细的工作负载的性能。
这是CreateVariableDopPropagator
方法及其相伴的VariableDopExecutionDataflowBlockOptions
类:
public class VariableDopExecutionDataflowBlockOptions : ExecutionDataflowBlockOptions
{
private readonly object _locker = new object();
public event EventHandler MaxDegreeOfParallelismChanged;
/// <summary>Gets the maximum number of messages that may be processed by the
/// block concurrently.</summary>
public new int MaxDegreeOfParallelism
{
get { lock (_locker) return base.MaxDegreeOfParallelism; }
set
{
bool changed;
lock (_locker)
{
changed = value != base.MaxDegreeOfParallelism;
base.MaxDegreeOfParallelism = value;
}
if (changed) MaxDegreeOfParallelismChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public static IPropagatorBlock<TInput, TOutput>
CreateVariableDopPropagator<TInput, TOutput>(
Func<TInput, Task<TOutput>> transform,
VariableDopExecutionDataflowBlockOptions dataflowBlockOptions)
{
if (transform == null) throw new ArgumentNullException(nameof(transform));
if (dataflowBlockOptions == null)
throw new ArgumentNullException(nameof(dataflowBlockOptions));
var optionsCopy = new ExecutionDataflowBlockOptions()
{
BoundedCapacity = dataflowBlockOptions.BoundedCapacity,
CancellationToken = dataflowBlockOptions.CancellationToken,
EnsureOrdered = dataflowBlockOptions.EnsureOrdered,
MaxDegreeOfParallelism = dataflowBlockOptions.MaxDegreeOfParallelism,
MaxMessagesPerTask = dataflowBlockOptions.MaxMessagesPerTask,
NameFormat = dataflowBlockOptions.NameFormat,
SingleProducerConstrained = dataflowBlockOptions.SingleProducerConstrained,
TaskScheduler = dataflowBlockOptions.TaskScheduler,
};
var locker = new object();
var input = new BufferBlock<TInput>(optionsCopy);
var output = new BufferBlock<TOutput>(optionsCopy);
PropagateFailure(output, input);
var propagateCompletion = new DataflowLinkOptions() { PropagateCompletion = true };
TransformBlock<TInput, TOutput> middle;
IDisposable link1 = null;
IDisposable link2 = null;
CreateMiddleBlock();
dataflowBlockOptions.MaxDegreeOfParallelismChanged += OnMaxDopChanged;
OnCompletion(output, () =>
{
dataflowBlockOptions.MaxDegreeOfParallelismChanged -= OnMaxDopChanged;
}, null);
return DataflowBlock.Encapsulate(input, output);
void CreateMiddleBlock()
{
IDataflowBlock localMiddle;
lock (locker)
{
link1?.Dispose();
link2?.Dispose();
var maxDop = dataflowBlockOptions.MaxDegreeOfParallelism;
optionsCopy.MaxDegreeOfParallelism = maxDop;
optionsCopy.BoundedCapacity = maxDop;
middle = new TransformBlock<TInput, TOutput>(transform, optionsCopy);
link1 = input.LinkTo(middle, propagateCompletion);
link2 = middle.LinkTo(output, propagateCompletion);
localMiddle = middle;
}
PropagateFailure(localMiddle, input); // Non disposable link, but it doesn't
// matter because the completion of the middle must be awaited anyway.
}
void OnMaxDopChanged(object sender, EventArgs e)
{
// Detach the middle block if it's not already detached
IDataflowBlock localMiddle;
lock (locker)
{
if (link1 == null) return;
link1.Dispose();
link2.Dispose();
link1 = null;
link2 = middle.LinkTo(output); // Without completion propagation
localMiddle = middle;
}
localMiddle.Complete();
OnCompletion(localMiddle, () => CreateMiddleBlock(), output);
}
async void PropagateFailure(IDataflowBlock block1, IDataflowBlock block2)
{
try { await block1.Completion.ConfigureAwait(false); }
catch (Exception ex) { if (!block1.Completion.IsCanceled) block2.Fault(ex); }
}
async void OnCompletion(IDataflowBlock block, Action action, IDataflowBlock onError)
{
try { await block.Completion.ConfigureAwait(false); }
catch { } // Ignore
finally
{
try { action(); }
catch (Exception ex) { if (onError != null) onError.Fault(ex); else throw; }
}
}
}
// Overload with synchronous delegate
public static IPropagatorBlock<TInput, TOutput>
CreateVariableDopPropagator<TInput, TOutput>(
Func<TInput, TOutput> transform,
VariableDopExecutionDataflowBlockOptions dataflowBlockOptions)
{
return CreateVariableDopPropagator<TInput, TOutput>(
item => Task.FromResult(transform(item)), dataflowBlockOptions);
}
用法示例:
private VariableDopExecutionDataflowBlockOptions _options;
private IPropagatorBlock<string, bool> _urlChecker;
private HttpClient _client = new HttpClient();
public Form1()
{
InitializeComponent();
_options = new VariableDopExecutionDataflowBlockOptions();
_urlChecker = CreateVariableDopPropagator<string, bool>(async (url) =>
{
return (await _client.GetAsync(url)).IsSuccessStatusCode;
}, _options);
}
private void ListBox1_SelectedValueChanged(object sender, EventArgs e)
{
_options.MaxDegreeOfParallelism = (int)ListBox1.SelectedValue;
}