В потоке данных TPL можно ли изменить DataflowBlockOptions после создания блока, но до его использования?

... и подействует ли это?

Я бы хотел отложить настройку свойства ExecutionDataflowBlockOptions.SingleProducerConstrained до тех пор, пока я не буду готов связать сеть вместе. (Потому что я хочу отделить создание блоков с их семантикой от соединения сети вместе с ее семантикой.)

Но, насколько я могу судить, вы можете установить ExecutionDataflowBlockOptions только при создании блока (например, для TransformBlock, TransformManyBlock и т. д. вы передаете его конструктору, и в противном случае он не отображается).

Однако... от меня не ускользнуло, что у свойств есть общедоступные сеттеры. Итак... могу ли я создать блок с экземпляром-заполнителем ExecutionDataflowBlockOptions и удерживать его, чтобы я мог позже установить SingleProducerConstrained=true, если захочу, при связывании блоков вместе (и что это вступит в силу)?

(Кстати, есть ли способ узнать, оказывает ли SingleProducerConstrained какое-либо влияние, кроме измерения пропускной способности?)

Обновление: @i3amon правильно указал в своем ответе, что это невозможно сделать, потому что блоки потока данных клонируют DataflowBlockOptions, который вы передаете, и используете его. Но я все равно сделал это, используя внутренние структуры данных, к которым я могу получить доступ через отражение и динамику. Я положил это в ответ ниже.


person davidbak    schedule 28.06.2014    source источник
comment
SingleProducerConstrained имеет эффект, но только в определенных ситуациях. Например, в ActionBlock действие должно быть синхронным, чтобы оно учитывалось.   -  person i3arnon    schedule 29.06.2014
comment
В моем случае я имею дело с синхронными действиями. Это связанный с вычислениями конвейер без асинхронных операций. Предыдущий ответ SO здесь показал, что это действительно может повысить пропускную способность в таких обстоятельствах.   -  person davidbak    schedule 29.06.2014


Ответы (2)


Это невозможно. Изменение параметров постфактум не сработает. Опции клонируются внутри конструктора блока. Изменение параметров позже не будет иметь никакого эффекта.

Вы можете увидеть примеры этого здесь и здесь и это просто проверить:

var options = new ExecutionDataflowBlockOptions
{
    NameFormat = "bar",
};
var block = new ActionBlock<int>(_ => { }, options);

options.NameFormat = "hamster";
Console.WriteLine(block.ToString());

Вывод:

бар

person i3arnon    schedule 28.06.2014
comment
Еще раз спасибо i3amon. На самом деле, прежде чем увидеть ваш ответ, я просто просмотрел dotnetinside.com (которому вы научили меня в своем ответе на мой последний вопрос!) и обнаружил, что он был клонирован. - person davidbak; 29.06.2014

Позвольте мне ответить на мой собственный вопрос. Используя информацию из декомпиляции DotNetInside сборки Dataflow, например, TransformBlock здесь (еще раз спасибо @i3amon за ссылку на dotnetinside.com) и очень хороший пакет ExposedObject на codeplex здесь (о котором я узнал на странице это сообщение в блоге, я сделал следующее:

  • Поток данных TPL блокирует все реализующие визуализаторы отладчика с помощью атрибута DebuggerTypeProxy, который применительно к типу дает имя другому типу для использования в отладчике Visual Studio всякий раз, когда должен отображаться исходный тип (например, окно просмотра).

  • Каждый из этих классов с именем DebuggerTypeProxy является внутренним классом блока потока данных, к которому прикреплен атрибут, обычно с именем DebugView. Этот класс всегда закрытый и запечатанный. Он раскрывает множество интересных вещей о блоке потока данных, включая его подлинный (не копию) DataflowBlockOptions, а также — если блок является исходным блоком — ITargetBlock[], который можно использовать для отслеживания сети потока данных от ее начального блока после построения.

  • Как только вы получите экземпляр DebugView, вы можете использовать dynamic через ExposedObject для получения любых свойств, предоставляемых классом - ExposedObject позволяет вам взять объект и использовать обычный метод и синтаксис свойств для доступа к его методам и свойствам.

  • Таким образом, вы можете получить DataflowBlockOptions из блока потока данных и изменить его NameFormat, а если это ExecutionDataflowBlockOptions (и вы еще не подключили блок к другим блокам), вы можете изменить его значение SingleProducerConstrained.

  • Однако вы не можете использовать dynamic для поиска или создания экземпляра внутреннего класса DebugView. Для этого нужно отражение. Вы начинаете с получения атрибута DebuggerTypeProxy из типа вашего блока потока данных, выбираете имя класса отладки, предполагаете, что это внутренний класс типа блока потока данных, и ищете его, преобразуете его в закрытый универсальный тип и, наконец, создаете экземпляр .

  • Будьте полностью осведомлены о том, что вы используете недокументированный код из внутренних компонентов потока данных. Используйте свое собственное суждение о том, является ли это хорошей идеей. На мой взгляд, разработчики TPL Dataflow проделали большую работу по поддержке просмотра этих блоков в отладчике, и они, вероятно, будут продолжать в том же духе. Детали могут измениться, но если вы выполняете правильную проверку ошибок при отражении и динамическом использовании этих типов, вы сможете обнаружить, когда ваш код перестанет работать с новой версией потока данных TPL.

Следующие фрагменты кода, вероятно, не компилируются вместе - они просто вырезаны и вставлены из моего рабочего кода из разных классов, но они, безусловно, дают вам представление. Я заставил это работать нормально. (Кроме того, для краткости я исключил все проверки ошибок.) (Кроме того, я разработал/протестировал этот код с версией 4.5.20.0 только для потока данных TPL, поэтому вам, возможно, придется адаптировать его для прошлых или будущих! - версий.)

// Set (change) the NameFormat of a dataflow block after construction
public void SetNameFormat(IDataflowBlock block, string nameFormat)
{
    try
    {
        dynamic debugView = block.GetInternalData(Logger);
        if (null != debugView)
        {
            var blockOptions = debugView.DataflowBlockOptions as DataflowBlockOptions;
            blockOptions.NameFormat = nameFormat;
        }
    }
    catch (Exception ex)
    {
        ...
    }
}

// Get access to the internal data of a dataflow block via its DebugTypeProxy class
public static dynamic GetInternalData(this IDataflowBlock block)
{
    Type blockType = block.GetType();
    try
    {
        // Get the DebuggerTypeProxy attribute, which names the debug class type.
        DebuggerTypeProxyAttribute debuggerTypeProxyAttr =
            blockType.GetCustomAttributes(true).OfType<DebuggerTypeProxyAttribute>().Single();

        // Get the name of the debug class type
        string debuggerTypeProxyNestedClassName =
            GetNestedTypeNameFromTypeProxyName(debuggerTypeProxyAttr.ProxyTypeName);

        // Get the actual Type of the nested class type (it will be open generic)
        Type openDebuggerTypeProxyNestedClass = blockType.GetNestedType(
            debuggerTypeProxyNestedClassName,
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);

        // Close it with the actual type arguments from the outer (dataflow block) Type.
        Type debuggerTypeProxyNestedClass =
            openDebuggerTypeProxyNestedClass.CloseNestedTypeOfClosedGeneric(blockType);

        // Now create an instance of the debug class directed at the given dataflow block.
        dynamic debugView = ExposedObject.New(debuggerTypeProxyNestedClass, block);

        return debugView;
    }
    catch (Exception ex)
    {
        ...
        return null;
    }
}

// Given a (Type of a) (open) inner class of a generic class, return the (Type
// of the) closed inner class.
public static Type CloseNestedTypeOfClosedGeneric(
                       this Type openNestedType,
                       Type closedOuterGenericType)
{
    Type[] outerGenericTypeArguments = closedOuterGenericType.GetGenericArguments();
    Type closedNestedType = openNestedType.MakeGenericType(outerGenericTypeArguments);
    return closedNestedType;
}

// A cheesy helper to pull a type name for a nested type out of a full assembly name.
private static string GetNestedTypeNameFromTypeProxyName(string value)
{
    // Expecting it to have the following form: full assembly name, e.g.,
    // "System.Threading...FooBlock`1+NESTEDNAMEHERE, System..."
    Match m = Regex.Match(value, @"^.*`\d+[+]([_\w-[0-9]][_\w]+),.*$", RegexOptions.IgnoreCase);
    if (!m.Success)
        return null;
    else
        return m.Groups[1].Value;
}
// Added to IgorO.ExposedObjectProject.ExposedObject class to let me construct an 
// object using a constructor with an argument.
public ExposedObject {
    ...

    public static dynamic New(Type type, object arg)
    {
        return new ExposedObject(Create(type, arg));
    }

    private static object Create(Type type, object arg)
    {
        // Create instance using Activator
        object res = Activator.CreateInstance(type, arg);
        return res;

        // ... or, alternatively, this works using reflection, your choice:
        Type argType = arg.GetType();
        ConstructorInfo constructorInfo = GetConstructorInfo(type, argType);
        return constructorInfo.Invoke(new object[] { arg });
    }
    ...
}
person davidbak    schedule 01.07.2014