问题
I'm writing an application that is using TPL dataflow. I'm trying to configure an action block to write to a database.
However, I need this action block to perform an initialization step on the first message it receives (note that I must wait for the first message and cannot perform the initialization during the action block creation).
Because of this, my action block needs to maintain some sort of state that indicates if its already received the first message.
Is it possible for an ActionBlock to maintain a state?
Referencing the Microsoft sample code below, how would I go about adding a state variable to the ActionBlock? It seems like it only maintains local variables.
// Performs several computations by using dataflow and returns the elapsed
// time required to perform the computations.
static TimeSpan TimeDataflowComputations(int maxDegreeOfParallelism,
int messageCount)
{
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<int>(
// Simulate work by suspending the current thread.
millisecondsTimeout => Thread.Sleep(millisecondsTimeout),
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = maxDegreeOfParallelism
});
// Compute the time that it takes for several messages to
// flow through the dataflow block.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < messageCount; i++)
{
workerBlock.Post(1000);
}
workerBlock.Complete();
// Wait for all messages to propagate through the network.
workerBlock.Completion.Wait();
// Stop the timer and return the elapsed number of milliseconds.
stopwatch.Stop();
return stopwatch.Elapsed;
}
回答1:
You could implement your own StatefulActionBlock<T>
, like so. Depending on your MaxDegreeOfParallelism
you may not need the lock (and even if you do there may be better ways of achieving thread-safety). Thanks to @TheodorZoulias for helping me refine this approach.
public class StatefulActionBlock<TInput, TState> : IDataflowBlock, ITargetBlock<TInput>
{
private bool _initialized;
private Action<TState> _initializer;
private object _lock = new object();
private ITargetBlock<TInput> _actionBlock;
private TState _state;
public Task Completion => _actionBlock.Completion;
public StatefulActionBlock(Action<TInput> action, Action<TState> initializer, TState state, ExecutionDataflowBlockOptions options)
{
//null checks omitted...
_initializer = initializer;
_actionBlock = new ActionBlock<TInput>(action, options);
_state = state;
}
void Initialize()
{
_initializer(_state);
_initialized = true;
}
public DataflowMessageStatus OfferMessage(DataflowMessageHeader messageHeader, TInput messageValue, ISourceBlock<TInput> source, bool consumeToAccept)
{
lock (_lock)
{
if (!_initialized)
Initialize();
}
return _actionBlock.OfferMessage(messageHeader, messageValue, source, consumeToAccept);
}
public void Complete() =>
_actionBlock.Complete();
public void Fault(Exception exception) =>
_actionBlock.Fault(exception);
}
You could also lock and check to see if you're initialized in your Action.
private static object _lock = new Object();
private static bool _isInitialized = false;
// Performs several computations by using dataflow and returns the elapsed
// time required to perform the computations.
static TimeSpan TimeDataflowComputations(int maxDegreeOfParallelism, int messageCount)
{
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<int>(
// Simulate work by suspending the current thread.
DoStuff,
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = maxDegreeOfParallelism
});
// Compute the time that it takes for several messages to
// flow through the dataflow block.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < messageCount; i++)
{
workerBlock.Post(1000);
}
workerBlock.Complete();
// Wait for all messages to propagate through the network.
workerBlock.Completion.Wait();
// Stop the timer and return the elapsed number of milliseconds.
stopwatch.Stop();
return stopwatch.Elapsed;
}
private static void DoStuff(int i)
{
lock (_lock)
{
if (!_initialized)
{
Initialize();
_initialized = true;
}
}
Thread.Sleep(i); //make a snack
}
private static void Initialize()
{
//...
}
来源:https://stackoverflow.com/questions/60620852/can-an-actionblock-contain-a-state