【问题标题】:Usage of Task.WaitAll without any 'await' operators causing warning CS1998 This async method lacks 'await' operators and will run synchronouslyTask.WaitAll 的使用没有任何“等待”运算符导致警告 CS1998 此异步方法缺少“等待”运算符,将同步运行
【发布时间】:2021-03-29 19:46:05
【问题描述】:

我想我的代码有很多弱点,所以请随时分享任何想法。 顺便说一句,我的主要问题是,当我尝试执行以下操作时,最后,当我想等待所有任务(通过使用 Task.WaitAll)完成以检查是否有任何异常时,真的会因为缺少“await”运算符而使代码的任何部分同步运行吗?

public class Program
{
    static bool mock = true;
    static readonly object s_lockSource = new object();
    static readonly CancellationTokenSource s_tokenSource = new CancellationTokenSource();
    static readonly CancellationToken s_token = s_tokenSource.Token;
    public static async Task Main()
    {
        var sw = new Stopwatch();
        sw.Start();

        IConfigurationRoot config = new ConfigurationBuilder()
            .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
            .AddJsonFile("appsettings.json").Build();

        var logger = new LiteLogger(new MemoryStream(), config);
        logger.Log(LogLevel.Debug, "Fetching data...");

        var jsonHelper = new JsonHelper();
        IAsyncIO fileService = mock ? new MockAsyncIO() : new FileService(config, s_token);
        IAsyncService webService = mock ? new MockAsyncIO() : new WebService(config, s_token);

        var remoteFetchTask = webService.FetchAsync(InOutOptions.None);
        var localFetchTask = fileService.FetchAsync(InOutOptions.ForecastPath);
        var todaysFetchTask = fileService.FetchAsync(InOutOptions.TodaysPath);

        var parseRemoteDataTask = remoteFetchTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Remote fetch task completed!");
                    logger.Log(LogLevel.Info, "Parsing remote data...");
                }
                return jsonHelper.FromJsonAsync<Region[]>(task.Result);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var filterRemoteRegionsTask = parseRemoteDataTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Forecasts parsing task completed!");
                    logger.Log(LogLevel.Info, "Merging data...");
                }

                Region[] remoteRegions = parseRemoteDataTask.Result.Result;
                return Task.Run(() => new DataFilter(config).FilterRegions(remoteRegions));
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var parseLocalDataTask = localFetchTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Local fetch task completed!");
                    logger.Log(LogLevel.Info, "Parsing local data...");
                }
                return jsonHelper.FromJsonAsync<Region>(task.Result);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var parseTodaysDataTask = todaysFetchTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Today's fetch task completed!");
                    logger.Log(LogLevel.Info, "Parsing today's data...");
                }
                return jsonHelper.FromJsonAsync<Forecast[]>(task.Result);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);


        var mergeTask =
            Task.WhenAll(filterRemoteRegionsTask, parseLocalDataTask)
            .ContinueWith(_ =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Forecasts parsing task completed!");
                    logger.Log(LogLevel.Info, "Merging data...");
                }

                Region localInstance = parseLocalDataTask.Result.Result;
                Region remoteInstance = filterRemoteRegionsTask.Result.Result;

                var dm = new DataMerger();

                return Task.Run(() => dm.MergeRegions(localInstance, remoteInstance));
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var forecastsSerializationTask = mergeTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "MergeTask completed!");
                    logger.Log(LogLevel.Info, "Serializing forecasts data...");
                }

                Region newLocalInstance = task.Result.Result;
                return jsonHelper.ToJsonAsync(newLocalInstance);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var forecastsStoreTask = forecastsSerializationTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Forecasts serialization task completed!");
                    logger.Log(LogLevel.Info, "Storing forecasts data...");
                }

                var newLocalJson = task.Result.Result;

                return fileService.PersistAsync(newLocalJson, InOutOptions.ForecastPath);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);


        var todaysDataBuildTask =
            Task.WhenAll(parseTodaysDataTask, filterRemoteRegionsTask)
            .ContinueWith(_ =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Today's weather parsing task completed!");
                    logger.Log(LogLevel.Info, "Building today's data...");
                }

                Region remoteInstance = filterRemoteRegionsTask.Result.Result;
                Forecast[] todaysWeathers = parseTodaysDataTask.Result.Result;

                var tdb = new TodaysDataBuilder(remoteInstance, todaysWeathers);
                return Task.Run(() => tdb.Build());
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var todaysDataSerializationTask = todaysDataBuildTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Today's weather data build task completed!");
                    logger.Log(LogLevel.Info, "Serializing today's data...");
                }

                return jsonHelper.ToJsonAsync(task.Result.Result);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var todaysDataStoreTask = todaysDataSerializationTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Today's weather data serialization task completed!");
                    logger.Log(LogLevel.Info, "Storing today's data...");
                }

                return fileService.PersistAsync(task.Result.Result, InOutOptions.TodaysPath);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);

        var uiDataBuildTask = Task.WhenAll(mergeTask, todaysDataBuildTask)
            .ContinueWith(_ =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Antecedent tasks completed!");
                    logger.Log(LogLevel.Info, "Building UI data source...");
                }

                var newLocalInstance = mergeTask.Result.Result;
                var newTodaysDatas = todaysDataBuildTask.Result.Result;

                var usb = new UiSourceBuilder(newLocalInstance, newTodaysDatas);
                return Task.Run(() => usb.Build());
            }, TaskContinuationOptions.OnlyOnRanToCompletion);


        var uiDataStoreTask = uiDataBuildTask
            .ContinueWith(task =>
            {
                lock (s_lockSource)
                {
                    logger.Log(LogLevel.Success, "Building UI data completed!");
                    logger.Log(LogLevel.Info, "Saving UI data to source file...");
                }

                return fileService.PersistAsync(task.Result.Result, InOutOptions.ResultPath);
            }, TaskContinuationOptions.OnlyOnRanToCompletion);


        try
        {
            Task.WaitAll(new Task[]
                {
            localFetchTask,
            remoteFetchTask,
            todaysFetchTask,
            parseLocalDataTask,
            parseRemoteDataTask,
            parseTodaysDataTask,
            mergeTask,
            forecastsStoreTask,
            todaysDataStoreTask,
            uiDataStoreTask
                });

            sw.Stop();
            var overall = sw.Elapsed.TotalSeconds;
            logger.Log(LogLevel.Success, "All task completed!");
            logger.Log(LogLevel.Info, $"Finished in {overall} second{(overall != 1 ? "s" : "")}");
            if (overall <= 1)
                logger.Log(LogLevel.Warn, "This application is too fast :)");
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.Flatten().InnerExceptions)
                logger.Log(LogLevel.Error,
                    $"Exception has been thrown at: {e.StackTrace}" +
                    $"{Environment.NewLine}\t\t{e.Message}");
        }
        catch (Exception ex)
        {
            logger.Log(LogLevel.Fatal, ex.ToString());
        }

        Console.WriteLine("\nPress any key to continue...");
        Console.ReadKey();
        logger.Dispose();
    }
}

Full source,如果需要了解更多信息。 也许值得一提的是,我使用的是 .NET 5.0。

【问题讨论】:

  • 标记为async 的方法通常会在某些时候执行await,但您的方法不会。所以发生的事情是该函数立即完成所有工作并返回一个已经完成的任务。对于Main,这并不重要。您可以从声明中删除 async 关键字。
  • 真正的挑战是如何使用适当的异步/等待代码重构这个ContinueWith galore!

标签: c# async-await console-application task-parallel-library .net-5


【解决方案1】:

真的会因为缺少'await'操作符而使代码的任何部分同步运行吗?

是的。 Main 方法将同步运行。这并不重要,因为它是 Main 方法,但如果您想异步等待任务完成,请使用 await Task.WhenAll 而不是 Task.WaitAll。异步方法还有一个额外的好处,它不会在AggregateException 中包装异常。

顺便说一句,use await instead of ContinueWith

【讨论】:

  • 首先,谢谢斯蒂芬的回答!但是,有一件事让我有点困惑。如果我没有误解,这里的第二个答案指出 WaitAll 是处理异常的正确选择。 stackoverflow.com/questions/6123406/waitall-vs-whenall
  • @NeMi:你真的需要知道所有的异常,还是一个就足够了?
  • 这是一个很好的问题。我认为答案是“是”,因为如果我从日志中发现在获取 API 时出现网络错误,我还想知道是否有任何错误,例如文件访问,因此我可以在一轮中解决这些问题,而不是再次运行失败,只是为了发现任何其他未决问题。你怎么看?
  • @NeMi:我从来不需要保存来自Task.WhenAll 的所有异常。可以通过保留从Task.WhenAll 返回的任务并在其出现故障时检查其Exception 来获取它们;那会比Task.WaitAll更好。
猜你喜欢
  • 2017-04-30
  • 1970-01-01
  • 2019-08-27
  • 2019-09-25
  • 2012-10-25
  • 2014-02-13
  • 2021-01-24
  • 2017-11-09
  • 1970-01-01
相关资源
最近更新 更多