【问题标题】:Completed c# Task Runs Again... Why?已完成的 c# 任务再次运行...为什么?
【发布时间】:2015-11-17 10:05:27
【问题描述】:

我对我的代码 [如下] 的行为感到有些困惑。我正在开发一个专门的命令行实用程序来下载和处理一些文件。我正在尝试尽可能使用 c# 的异步功能。创建任务并使用 Task.WaitAll() 时,代码 sn-p 会按预期运行。等待后,我有 2 个任务都被标记为已完成。问题:我尝试从任务中获取结果最终会再次运行这两个任务!为什么是这样?如何在不执行第二次任务的情况下读取结果?

    private IEnumerable<Task<FileInfo>> DownloadFiles()
    {
        int fileCount = 1;

        Console.Clear();
        Console.SetCursorPosition(0, 0);
        Console.Write("Download files...");

        yield return DownloadFile(Options.SkuLookupUrl, "SkuLookup.txt.gz", fileCount++, f =>
        {
            return DecompressFile(f);
        });
        yield return DownloadFile(Options.ProductLookupUrl, "ProductList.txt.gz", fileCount++, f =>
        {
            return DecompressFile(f);
        });
    }

    public void Execute()
    {
        var tasks = DownloadFiles();
        Task.WaitAll(tasks.ToArray());

        Console.WriteLine();
        Console.WriteLine("Download(s) completed.  Parsing sku lookup file.");
        FileInfo[] files = tasks.Select(t => t.Result).ToArray(); // <-- triggers a second round of task execution

        ParseSkuLookups(files.SingleOrDefault(f => f.Name.ToLowerInvariant().Contains("skulookup")));
    }

如果相关的话,这里是下载方法:

    private async Task<FileInfo> DownloadFile(string targetUrl, string destinationFile, int lineNumber, Func<FileInfo,FileInfo> callback = null)
    {
        FileInfo fi = new FileInfo(destinationFile);

        if (!Options.NoCleanup || !fi.Exists)
        {
            WebClient client = new WebClient();
            client.DownloadProgressChanged += (s, e) =>
            {
                char spinnerChar;

                switch ((e.ProgressPercentage % 10))
                {
                    case 0: spinnerChar = '|'; break;
                    case 1: spinnerChar = '/'; break;
                    case 2: spinnerChar = '-'; break;
                    case 3: spinnerChar = '|'; break;
                    case 4: spinnerChar = '\\'; break;
                    case 5: spinnerChar = '|'; break;
                    case 6: spinnerChar = '/'; break;
                    case 7: spinnerChar = '-'; break;
                    case 8: spinnerChar = '\\'; break;
                    default:
                    case 9: spinnerChar = '|'; break;

                }
                lock (ConsoleLockSync)
                {
                    Console.SetCursorPosition(0, lineNumber);
                    Console.WriteLine(String.Format("{0} download: {1}% {2}", 
                        destinationFile, e.ProgressPercentage==100 ? "[Complete]" : spinnerChar.ToString()));
                }
            };
            await client.DownloadFileTaskAsync(new Uri(targetUrl, UriKind.Absolute), destinationFile);
        }
        else if(Options.NoCleanup)
        {
            lock (ConsoleLockSync)
            {
                Console.SetCursorPosition(0, lineNumber);
                Console.WriteLine(String.Format("{0} download: Skipped [No Cleanup]        ", destinationFile));
            }
        }
        fi.Refresh();
        return callback != null ? callback(fi) : fi;
    }

【问题讨论】:

  • 哦,顺便说一句。在异步方法上调用 .WaitAll( 很容易让你陷入僵局。您应该让 Execute 返回 async Task 并执行 await Task.WhenAll(tasks) 或在 DownloadFile 内的每个 await 调用中使用 .ConfigureAwait(false)
  • 一个价格的两个答案......不过,这是一个控制台应用程序,所以我并不太担心死锁,但这对我在其他地方会有帮助。再次感谢!

标签: c# c#-4.0 async-await


【解决方案1】:

当您每次枚举结果时都使用yield return 实现IEnumerable 时,它将重新运行您的函数。 tasks.ToArray()Task.WaitAll(tasks.ToArray()); 中枚举一次,然后在tasks.Select(t =&gt; t.Result).ToArray(); 中再次枚举。要让它枚举一次,保留第一个 ToArray() 调用的结果,然后在整个方法中重复使用该结果。

public void Execute()
{
    var tasks = DownloadFiles();
    var taskArray = tasks.ToArray();
    Task.WaitAll(taskArray);

    Console.WriteLine();
    Console.WriteLine("Download(s) completed.  Parsing sku lookup file.");
    FileInfo[] files = taskArray.Select(t => t.Result).ToArray(); // <-- notice we use taskArray here instead of tasks.

    ParseSkuLookups(files.SingleOrDefault(f => f.Name.ToLowerInvariant().Contains("skulookup")));
}

【讨论】:

  • 哇...我不知道收益返回结果会这样(只是为了熟悉该功能而工作)。谢谢你修复它!
猜你喜欢
  • 2019-09-23
  • 1970-01-01
  • 1970-01-01
  • 2015-10-04
  • 1970-01-01
  • 2016-01-03
  • 1970-01-01
  • 1970-01-01
  • 2021-02-15
相关资源
最近更新 更多