В 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());
Выход:
бар