如何更改现有TPL块的MaxDegreeOfParallelism?

时间:2018-04-11 19:18:43

标签: c# task-parallel-library tpl-dataflow

我用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。我没有办法这样做,因为没有公共财产允许我改变它。有办法吗?

2 个答案:

答案 0 :(得分:2)

根据MSDN

  

数据流阻止捕获构造选项的状态。对提供的ExecutionDataflowBlockOptions实例的后续更改不应影响数据流块的行为

所以,回答你的问题,

  

我想改变转换块实例的MaxDegreeOfParallelism

它不适合现有的块来更改它的选项。您要么应该创建新管道,要么创建新管道,或者从头开始调整此设置以获得最大利润。

答案 1 :(得分:0)

我试图通过使用可交换的TransformBlock(夹在两个BufferBlock之间)来解决此问题。每次更改MaxDegreeOfParallelism时,都会分离旧的TransformBlock,标记为已完成,等待其完成,然后在其位置附加新的TransformBlock。该解决方案有两个缺点:

  1. 在替换中间块的间歇阶段,并行度逐渐降低到零。

  2. 中间块的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;
}