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

... и это вступит в силу?

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

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

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

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

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

2 ответа

Решение

Позвольте мне ответить на мой собственный вопрос. Используя информацию из декомпиляции 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 Dataflow.

Следующие фрагменты кода, вероятно, не компилируются вместе - они просто вырезаны и вставлены из моего рабочего кода, из разных классов, но они, безусловно, дают вам идею. Я сделал это работает нормально. (Также, для краткости, я исключил всю проверку ошибок.) (Также я разработал / протестировал этот код только с версией 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 });
    }
    ...
}

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

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

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

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

Выход:

бар

Другие вопросы по тегам