![](/img/trans.png)
[英]Right way of using TPL for this specific constellation ( Continuous parallel Tasks )
[英]Using TPL to run Tasks in parallel
我有一个简单的(只是测试)状态机,它接受以下输入字符串abc
和ac
。 状态机设置如下:
s1 --> 'a' --> s2
s2 --> 'b' --> s3
s3 --> 'c' --> s4
s2 --> s4 (Epsilon transition)
s1是开始状态
s4是接受状态
我想使用TPL s1->s2->s3->s4
执行s1->s2->s3->s4
和s1->s2->s3->s4
(彼此独立)。
如果我输入“ abc”作为机器接受的输入,即
> Thread 1 - Consumed: a, from State: 1 to State: 2
> Thread 2 - Consumed: b, from State: 2 to State: 3
> Thread 3 - Epsilon transition from State: 2 to State: 3
> Thread 4 - Consumed: c, from State: 3 to State: 4
> Thread 4 - Accepted in state 4
Time taken = 19
Input 'abc' is valid
Press any key to exit
但是,如果我输入“ ac”,则会得到以下信息:
> Thread 1 - Consumed: a, from State: 1 to State: 2
> Thread 2 - Epsilon transition from State: 2 to State: 3
> Thread 3 - Consumed: c, from State: 3 to State: 4
> Thread 3 - Accepted in state 4
> Thread 4 - Consumed: c, from State: 3 to State: 4
> Thread 4 - Accepted in state 4
Time taken = 39
Input 'ac' is not valid (Reason: RejectedAmbiguous)
Press any key to exit
由于某种原因,状态机两次接受相同的输入(在状态4中接受),这是不可能的,因为并行执行的两行都接受不同的输入。
我不会发布所有代码,因为它们太多了,但是我会发布主要代码,以便您了解我在做什么错。
public enum eResult
{
Accepted = 0,
RejectedAmbiguous,
RejectedNoResults,
RejectedNoInitialState
}
public eResult Execute()
{
var startState = States.FirstOrDefault(s => s.Initial);
if (startState == null) return eResult.RejectedNoInitialState;
tasks.Clear();
CancellationTokenSource cts = new CancellationTokenSource();
Task t = new Task(() =>
{
foreach(Transition tr in getTransitions(startState))
{
var tr = trans[n];
var actor = new Actor(tr.FromState, this.input);
Task<Actor> task = Task<Actor>.Factory.StartNew(obj =>
{
return doTransitionFunction(tr, cts).Invoke((Actor)obj);
}, actor, cts.Token);
buildContinuationTask(Transitions[tr], task, cts);
tasks.Add(task);
}
}, cts.Token);
t.RunSynchronously();
try
{
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae)
{
foreach (Exception e in ae.Flatten().InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
eResult result = eResult.Accepted;
if (!results.Any()) result = eResult.RejectedNoResults;
else if (results.Where(r => r.State.Accepted).Count() > 1) result = eResult.RejectedAmbiguous;
return result;
}
IEnumerable<Transition> getTransitions(AtomicState state)
{
return Transitions.Keys.Where(k => k.FromState == state);
}
bool isAccept(Actor parcel)
{
return (parcel.State.Accepted && parcel.Cursor.EOF());
}
Func<object, Actor> doTransitionFunction(Transition transition, CancellationTokenSource cts)
{
return new Func<object, Actor>(obj =>
{
var ts = (Actor)obj;
var cur = ts.Cursor.Peek();
if (transition.Epsilon || transition.Input.Invoke() == cur)
{
if (!transition.Epsilon) ts.Cursor.MoveNext();
ts.State = Transitions[transition];
OnTransitioned(this, new TransitionedEventArgs(transition.FromState, ts.State, cur, transition.Epsilon, Task.CurrentId));
if (isAccept(ts))
{
OnAccepted(this, new AcceptedEventArgs(ts.State, Task.CurrentId));
results.Add(ts);
cts.Cancel();
}
}
return ts;
});
}
void buildContinuationTask(AtomicState s, Task<Actor> antecedentTask, CancellationTokenSource cts)
{
var trans = getTransitions(s).ToArray();
for (int n = 0; n < trans.Count(); n++)
{
Transition tr = trans[n];
Task<Actor> continuation = antecedentTask.ContinueWith<Actor>(antecdent =>
{
if (!cts.IsCancellationRequested)
return doTransitionFunction(tr, cts).Invoke((Actor)antecdent.Result.Clone());
else
return (Actor)antecdent.Result.Clone();
}, cts.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
buildContinuationTask(Transitions[tr], continuation, cts);
tasks.Add(continuation);
}
}
如果这不可能,请纠正我,但是我想做的是:
对于第一个并行任务,接受abc
作为输入:
s1是Task<Actor>
s2是s1的延续
s3是s2的延续
s4是s3的延续
要第二个并行任务接受ac
:
s1是Task<Actor>
s2是s1的延续
s3是s2的延续(这是epsilon移动)
s4是s3的延续
这两个任务都有自己的Actor
对象副本,该副本将从主要的先前任务传递到继续任务中。
我知道我快到了,我只需要解决最后一个谜团。
在阅读了有关TPL DataFlow的信息后,这是我的尝试,并且似乎可以按照我想要的方式工作。
public interface IScrollableCursor
{
void MoveNext();
void MovePrevious();
void MoveFirst();
void MoveLast();
bool BOF();
bool EOF();
char Peek();
int CurrentPosition { get; }
}
[Serializable]
public abstract class AtomicState
{
protected int stateId;
protected bool accepted;
public int StateId
{
get
{
return stateId;
}
}
public AtomicState( int stateId )
{
this.stateId = stateId;
this.accepted = false;
}
public AtomicState( int stateId, bool accepted )
: this( stateId )
{
this.accepted = accepted;
}
public abstract bool Initial { get; }
public bool Accepted
{
get
{
return accepted;
}
}
}
[Serializable]
public struct Actor : ICloneable
{
private AtomicState state;
private IScrollableCursor cursor;
public AtomicState State
{
get
{
return state;
}
set
{
state = value;
}
}
public IScrollableCursor Cursor
{
get
{
return cursor;
}
}
public Actor( AtomicState state, IScrollableCursor cursor )
{
this.state = state;
this.cursor = cursor;
}
public object Clone()
{
return this.DeepClone();
}
}
public class Transition
{
protected AtomicState fromState;
protected Func<Char> input;
protected bool epsilon;
public AtomicState FromState
{
get
{
return fromState;
}
}
public Func<Char> Input
{
get
{
return input;
}
}
public bool Epsilon
{
get
{
return epsilon;
}
}
public Transition( AtomicState fromState, Func<Char> input )
{
this.fromState = fromState;
this.input = input;
}
public Transition( AtomicState fromState, bool epsilon )
: this( fromState, null )
{
this.epsilon = epsilon;
}
}
public class EpsilonTransition : Transition
{
public EpsilonTransition( AtomicState fromState )
: base( fromState, true )
{
}
}
public eResult Execute()
{
var startState = States.FirstOrDefault( s => s.Initial );
if ( startState == null ) return eResult.RejectedNoInitialState;
tasks.Clear();
CancellationTokenSource cts = new CancellationTokenSource();
ExecutionDataflowBlockOptions options = new ExecutionDataflowBlockOptions();
options.MaxDegreeOfParallelism = 4;
options.CancellationToken = cts.Token;
// transitions an actor onto it's next state
TransformBlock<Tuple<Transition, Actor>, Actor> actorTransitioner = new TransformBlock<Tuple<Transition, Actor>, Actor>( tr =>
{
return doTransitionFunction( tr.Item1, cts ).Invoke( tr.Item2 );
}, options );
BroadcastBlock<Actor> actorTransitionerBroadcaster = new BroadcastBlock<Actor>( a => { return a; } );
ActionBlock<Actor> actorProcessor = new ActionBlock<Actor>( a =>
{
foreach ( Transition t in getTransitions( a.State ) )
{
actorTransitioner.Post( new Tuple<Transition, Actor>( t, (Actor)a.Clone() ) );
}
} );
// link blocks
actorTransitioner.LinkTo( actorTransitionerBroadcaster );
actorTransitionerBroadcaster.LinkTo( actorProcessor );
actorTransitionerBroadcaster.Post( new Actor( startState, input ) );
try
{
actorTransitioner.Completion.Wait();
}
catch ( AggregateException ex )
{
foreach ( Exception ae in ex.Flatten().InnerExceptions )
{
Console.WriteLine( ae.Message );
}
}
eResult result = eResult.Accepted;
if ( !results.Any() ) result = eResult.RejectedNoResults;
else if ( results.Where( r => r.State.Accepted ).Count() > 1 ) result = eResult.RejectedAmbiguous;
return result;
}
我已经添加了尝试中使用的结构,因此更易于复制。 有必要我将发布整个代码(大约12个类)。
在提出一个简单得多的解决方案之后,我设法回答了自己的问题。 我推断TPL DataFlow不适合此操作,因为它会创建循环数据网络,而无法确定计算是否已完成。 因此,我决定将其完全装箱并返回到绘图板。
最终,我发现了Paralell.ForEach()完全满足我的要求,并利用所有处理器内核并行运行每个转换:
public eResult Execute()
{
var startState = States.FirstOrDefault( s => s.Initial );
if ( startState == null ) return eResult.RejectedNoInitialState;
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task t = new Task( () =>
{
Parallel.ForEach( getTransitions( startState ), new ParallelOptions { MaxDegreeOfParallelism = 4 }, tr =>
{
var a0 = new Actor( tr.FromState, (IScrollableCursor)this.input.DeepClone() );
var a1 = doTransitionFunction( tr, cts ).Invoke( a0 );
if ( a0.State != a1.State )
processRecursively( a1.State, a0, cts );
} );
}, cts.Token );
t.RunSynchronously();
eResult result = eResult.Accepted;
if ( !results.Any() ) result = eResult.RejectedNoResults;
else if ( results.Where( r => r.State.Accepted ).Count() > 1 ) result = eResult.RejectedAmbiguous;
return result;
}
void processRecursively( AtomicState s, Actor a0, CancellationTokenSource cts )
{
Parallel.ForEach( getTransitions( s ), tr =>
{
var a1 = doTransitionFunction( tr, cts ).Invoke( a0 );
if ( a0.State != a1.State )
processRecursively( a1.State, a1, cts );
} );
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.