[英]Why tasks are not going in parallel?
我有多个运行测试的位置(称为单元)。 测试被实现为异步任务并因此运行。 用户可以选择为每个单元运行任何测试。 如果我选择在所有单元上运行完全相同的测试,那么它将或多或少并行进行。
具有测试A, B, C
,如果在单元1和2上选择测试A, B
而在3上我仅选择C
,则由于某种原因,单元1和2中的测试将开始运行,但是在单元3中测试C将不运行开始,直到在单元格1和2中的A和B测试不会完成。 基本上,所有单元中的所有测试都倾向于以相同的顺序运行。 那不是我想要的。 我试图实现的是独立于每个单元运行的测试链。 现在,我将展示如何实现。
private async void buttonStartTest_Click(object sender, EventArgs e)
{
var cells = objectListView.CheckedObjects.Cast<Cell>().ToList();
if (cells == null)
return;
var blockPrepare = CreateExceptionCatchingTransformBlock(new Func<Cell, Task<Cell>>(Tests.Prepare), new Action<Exception, Cell>(HandleUnhandledException), new ExecutionDataflowBlockOptions
{
BoundedCapacity = 10000,
MaxDegreeOfParallelism = 40,
});
var blockFinalize = CreateExceptionCatchingActionBlock(new Func<Cell, Task>(Tests.Finalize), new Action<Exception, Cell>(HandleUnhandledException), new ExecutionDataflowBlockOptions
{
BoundedCapacity = 10000,
MaxDegreeOfParallelism = 40,
});
List<IPropagatorBlock<Cell, Cell>> blockList = new List<IPropagatorBlock<Cell, Cell>>();
var funcs = tests.Select(x => x.Value);
foreach (var func in funcs)
{
var blockNew = CreateExceptionCatchingTransformBlock(new Func<Cell, Task<Cell>>(func), new Action<Exception, Cell>(HandleUnhandledException), new ExecutionDataflowBlockOptions
{
BoundedCapacity = 10000,
MaxDegreeOfParallelism = 40,
});
blockList.Add(blockNew);
}
// link
for (int i = 0; i < blockList.Count - 1; i++)
{
var b1 = blockList[i];
var b2 = blockList[i + 1];
b1.LinkTo(b2);
}
// link first and last
blockPrepare.LinkTo(blockList[0], new DataflowLinkOptions { PropagateCompletion = true });
blockList[blockList.Count - 1].LinkTo(blockFinalize, new DataflowLinkOptions { PropagateCompletion = true });
foreach (Cell c in cells)
{
c.Reset();
c.State = Cell.States.InProgress;
var progressHandler = new Progress<string>(value =>
{
c.Status = value;
});
c.Progress = progressHandler as IProgress<string>;
blockPrepare.Post(c);
};
blockPrepare.Complete();
try
{
await blockFinalize.Completion;
}
catch (Exception ex)
{
logger.Debug(ex.InnerException.InnerException.Message);
}
}
在上方,您可以看到每个单元格有2个必需块-准备并完成。 这是我创建它们的方法:
public IPropagatorBlock<TInput, TOutput> CreateExceptionCatchingTransformBlock<TInput, TOutput>(
Func<TInput, Task<TOutput>> transform,
Action<Exception, Cell> exceptionHandler,
ExecutionDataflowBlockOptions dataflowBlockOptions)
{
return new TransformManyBlock<TInput, TOutput>(async input =>
{
try
{
var result = await transform(input);
return new[] { result };
}
catch (Exception ex)
{
exceptionHandler(ex, (input as Cell));
return Enumerable.Empty<TOutput>();
}
}, dataflowBlockOptions);
}
public ITargetBlock<TInput> CreateExceptionCatchingActionBlock<TInput>(
Func<TInput, Task> action,
Action<Exception, Cell> exceptionHandler,
ExecutionDataflowBlockOptions dataflowBlockOptions)
{
return new ActionBlock<TInput>(async input =>
{
try
{
await action(input);
}
catch (Exception ex)
{
exceptionHandler(ex, (input as Cell));
}
}, dataflowBlockOptions);
}
测试本身如下所示:
public static async Task<Cell> TestDoorsAsync(Cell c)
{
int thisTestID = TEST_DOORS;
TestConfiguration conf = c.GetConfiguration(thisTestID);
if (conf.Enabled)
{
... // execute test
}
else
{
// report that test was skipped due to user configuration
}
return c;
}
那么,我是否错过了一些选择,或者软件设计错误,从而阻止了单元中的测试运行而无需等待其他单元中的测试完成?
UPDATE
回购是证明问题的最小控制台应用程序。
仍然有3个单元格和3个测试(任务)。 在单元1、2上,我选择运行所有测试,而在单元3上仅选择测试3。我期望在单元3的准备任务完成后立即看到跳过的测试1、2和运行测试3。
我看到的是(#-手机号码)
#1 Preparing...
#2 Preparing...
#3 Preparing...
#1 Test1 running...
#2 Test1 running...
#3 Test1 skipped
#1 Test2 running...
#2 Test2 running...
#3 Test2 skipped
#1 Test3 running...
#2 Test3 running...
#3 Test3 running...
#2 Finalizing...
#1 Finalizing...
#3 Finalizing...
单元3中的测试与单元1和2中的测试同步。所有测试同时完成,而单元3中的单个测试应该比其他单元中的测试更早完成。
感谢您的修改。 添加EnsureOrdered = false
可阻止选项。 发生的情况是您的TransfomrBlocks
直到所有单元都完成处理后才传递单元格,因此它们可以维持您的订单。 这是默认设置,通常是可取的,但不适用于您的情况。
当我评论他们在当前代码中没有错时,看起来我错了。
很难肯定地说出来,但是我可以肯定地在您的代码中看到两个缺陷:
.Post
而不是SendAsync
,这显然是您在此处获取异步流所需要的。 因此,最后一个必须等待第一个完成。 另外,您需要了解使用BoundedCapacity
在您的管道中引入限制,因此您应该检查缓冲区大小,也许很多线程只是在等待队列中的可用空间。
您可以尝试的另一件事是对DataflowBlockOptions.MaxMessagesPerTask
属性进行级别调整。 此属性用于一种贪婪块非常快速地执行并处理越来越多的消息而又不让其他块完成其工作的情况。 在内部,每个块都有一个Task
,在其中完成处理,默认值为-1,表示消息数量不受限制。 通过将此值设置为某个正数,可以强制该块重新启动其内部任务,并为其他块提供一些空间。
有关更多高级技巧,请参阅官方文档 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.