繁体   English   中英

如何在完成先前任务时执行多个并行任务

[英]How to execute multiple parallel tasks on completion of a prior task

我遇到一种情况,需要调用Web服务,并在成功完成后使用从Web服务返回的结果执行多项操作。 我已经开发了“有效”的代码-只是没有达到我的预期。 具体来说,我想从对Web服务的调用中获取结果,并将这些结果传递给多个要并行执行的连续任务,但是我现在拥有的是在执行第二个任务之前执行第一个连续任务。

我整理了一个有关当前正在执行的简化示例,希望可以帮助说明这种情况。 一,执行:

public interface IConfigurationSettings
{
    int? ConfigurationSetting { get; set; }
}

public interface IPrintCommandHandler
{
    System.Threading.Tasks.Task<bool> ExecuteAsync(byte[] reportContent);
}

public interface ISaveCommandHandler
{
    System.Threading.Tasks.Task<bool> ExecuteAsync(byte[] reportContent);
}

public interface IWebService
{
    System.Threading.Tasks.Task<object> RetrieveReportAsync(string searchToken, string reportFormat);
}

public class ReportCommandHandler
{
    private readonly IConfigurationSettings _configurationSettings;
    private readonly IPrintCommandHandler _printCommandHandler;
    private readonly ISaveCommandHandler _saveCommandHandler;
    private readonly IWebService _webService;

    public ReportCommandHandler(IWebService webService, IPrintCommandHandler printCommandHandler, ISaveCommandHandler saveCommandHandler, IConfigurationSettings configurationSettings)
    {
        _webService = webService;
        _printCommandHandler = printCommandHandler;
        _saveCommandHandler = saveCommandHandler;
        _configurationSettings = configurationSettings;
    }

    public async Task<bool> ExecuteAsync(string searchToken)
    {
        var reportTask = _webService.RetrieveReportAsync(searchToken, "PDF");
        var nextStepTasks = new List<Task<bool>>();

        // Run "print" task after report task.
        var printTask = await reportTask.ContinueWith(task => _printCommandHandler.ExecuteAsync((byte[]) task.Result));
        nextStepTasks.Add(printTask);

        // Run "save" task after report task.
        if (_configurationSettings.ConfigurationSetting.HasValue)
        {
            var saveTask = await reportTask.ContinueWith(task => _saveCommandHandler.ExecuteAsync((byte[]) task.Result));
            nextStepTasks.Add(saveTask);
        }

        var reportTaskResult = await Task.WhenAll(nextStepTasks);
        return reportTaskResult.Aggregate(true, (current, result) => current & result);
    }
}

因此,Web服务(第三方,与我无关)具有用于执行搜索/查找的端点,如果成功,该端点将返回参考号(在我的示例中,我将其称为搜索令牌)。 然后,使用该参考号以几种不同格式中的任何一种来检索查找结果(使用不同的端点)。

在此示例中, IWebService接口表示我创建的用于管理与Web服务的交互的应用程序服务。 实际的实现还有其他方法可以进行查找,ping等操作。

只是为了使事情变得有趣,需要一个连续的任务(将始终在主要任务之后执行),但是另一个连续的任务是可选的,执行取决于应用程序中其他位置的配置设置。

为了更容易地演示该问题,我创建了一个单元测试:

public class RhinoMockRepository : IDisposable
{
    private readonly ArrayList _mockObjectRepository;

    public RhinoMockRepository()
    {
        _mockObjectRepository = new ArrayList();
    }

    public T CreateMock<T>() where T : class
    {
        var mock = MockRepository.GenerateMock<T>();
        _mockObjectRepository.Add(mock);
        return mock;
    }

    public T CreateStub<T>() where T : class
    {
        return MockRepository.GenerateStub<T>();
    }

    public void Dispose()
    {
        foreach (var obj in _mockObjectRepository) obj.VerifyAllExpectations();
        _mockObjectRepository.Clear();
    }
}

[TestFixture]
public class TapTest
{
    private const string SearchToken = "F71C8B50-ECD1-4C02-AD3F-6C24F1AF3D9A";

    [Test]
    public void ReportCommandExecutesPrintAndSave()
    {
        using (var repository = new RhinoMockRepository())
        {
            // Arrange
            const string reportContent = "This is a PDF file.";
            var reportContentBytes = System.Text.Encoding.Default.GetBytes(reportContent);

            var retrieveReportResult = System.Threading.Tasks.Task.FromResult<object>(reportContentBytes);
            var webServiceMock = repository.CreateMock<IWebService>();
            webServiceMock.Stub(x => x.RetrieveReportAsync(SearchToken, "PDF")).Return(retrieveReportResult);

            var printCommandHandlerMock = repository.CreateMock<IPrintCommandHandler>();
            var printResult = System.Threading.Tasks.Task.FromResult(true);
            printCommandHandlerMock
                .Expect(x => x.ExecuteAsync(reportContentBytes))
                //.WhenCalled(method => System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)))
                .Return(printResult);

            var configurationSettingsStub = repository.CreateStub<IConfigurationSettings>();
            configurationSettingsStub.ConfigurationSetting = 10;

            var saveCommandHandlerMock = repository.CreateMock<ISaveCommandHandler>();
            var saveResult = System.Threading.Tasks.Task.FromResult(true);
            saveCommandHandlerMock.Expect(x => x.ExecuteAsync(reportContentBytes)).Return(saveResult);

            // Act
            var reportCommandHandler = new ReportCommandHandler(webServiceMock, printCommandHandlerMock, saveCommandHandlerMock, configurationSettingsStub);
            var result = System.Threading.Tasks.Task
                .Run(async () => await reportCommandHandler.ExecuteAsync(SearchToken))
                .Result;

            // Assert
            Assert.That(result, Is.True);
        }
    }
}

理想情况下,在IWebService上完成对RetrieveReportAsync()的调用后,应该同时执行“打印”和“保存”命令处理程序,并从RetrieveReportAsync()接收到结果的副本。 但是,如果未注释单元测试中对WhenCalled ...的调用,并且逐步执行ReportCommandHandler.ExecuteAsync() ,则可以看到“ print”命令在到达“保存”之前已执行并完成。命令。 现在,我知道await的全部要点是暂停执行async方法的调用,直到await代码完成为止,但是我不清楚如何同时实例化“打印”和“保存”命令(任务)作为“ report”任务的延续,以便它们在“ report”任务完成时都并行执行,然后“ report”命令可以返回基于“ print”和“ result”结果的结果“保存”命令(任务)。

您的问题确实涉及解决两个不同的目标:

  1. 如何等待任务?
  2. 如何同时执行另外两个任务?

我发现在代码中混淆了awaitContinueWith() 我不清楚您为什么这么做。 await您完成的关键任务之一是自动设置一个延续,因此您不必显式调用ContinueWith() 但是,您还是要这样做。

出于对错误的认识,由于对如何实现目标缺乏全面的了解,因此我将按照以下方式编写您的方法:

public async Task<bool> ExecuteAsync(string searchToken)
{
    var reportTaskResult = await _webService.RetrieveReportAsync(searchToken, "PDF");
    var nextStepTasks = new List<Task<bool>>();

    // Run "print" task after report task.
    var printTask = _printCommandHandler.ExecuteAsync((byte[]) reportTaskResult);
    nextStepTasks.Add(printTask);

    // Run "save" task after report task.
    if (_configurationSettings.ConfigurationSetting.HasValue)
    {
        var saveTask = _saveCommandHandler.ExecuteAsync((byte[]) reportTaskResult);
        nextStepTasks.Add(saveTask);
    }

    var reportTaskResult = await Task.WhenAll(nextStepTasks);
    return reportTaskResult.Aggregate(false, (current, result) => current | result);
}

换句话说,请先await原始任务。 然后,您知道它已完成并产生了结果。 那时,请继续并启动其他任务,将其Task对象添加到列表中,但不要单独等待每个Task 最后, await任务的完整列表。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM