.NET 6 WinForms中Parallel.ForEach参数错误:ParallelLoopState转CancellationToken失败
解决CS1503:无法从ParallelLoopState转换为CancellationToken的错误
错误原因
你在Parallel.ForEach的异步委托里,把第二个参数命名为token,但这个参数的实际类型是**ParallelLoopState**,并非CancellationToken。当你把它传给GetByteArrayAsync的第二个参数时,就会触发类型不匹配的CS1503错误。
另外还有一个隐藏问题:Parallel.ForEach是为CPU密集型同步操作设计的,不适合IO密集型的异步网络请求,直接混用会导致并行任务无法被正确等待(属于"火并忘记"的场景),最终会出现IsBusy提前置为false、任务未完成就返回结果的问题。
修复方案
1. 修正参数命名并传递正确的取消令牌
把Parallel.ForEach委托里的token参数重命名为loopState,调用GetByteArrayAsync时传入外层的cts.Token。
2. 替换Parallel.ForEach为Task.WhenAll实现异步并行
改用Task.WhenAll结合Enumerable.Select来实现真正的异步并行,同时能正确等待所有任务完成。如果需要限制并行度,可以搭配SemaphoreSlim控制同时运行的任务数量。
修改后的完整代码
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; namespace WinFormsApp1 { public class ResourceDownloader { private static Lazy<HttpClient> client = new(() => { HttpClientHandler handler = CreateHandler(autoRedirect: true); var client = new HttpClient(handler, true) { Timeout = TimeSpan.FromSeconds(60) }; client.DefaultRequestHeaders.Add("User-Agent", @"Mozilla/5.0 (Windows NT 10; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"); client.DefaultRequestHeaders.Add("Cache-Control", "no-cache"); client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate"); client.DefaultRequestHeaders.ConnectionClose = true; return client; }, true); private static HttpClientHandler CreateHandler(bool autoRedirect) { return new HttpClientHandler() { AllowAutoRedirect = autoRedirect, CookieContainer = new CookieContainer(), AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; } public record Website(string Url, byte[]? Data, bool Completed = true, Exception? Ex = null); public record ProgressReport(Website Site, int PercentageComplete); private static object syncObj = new object(); private static ConcurrentBag<Website> processed = default!; private static int progressCount = 0; private static int totalCount = 0; public static bool IsBusy { get; internal set; } = false; public static async Task<List<Website>> Download(IProgress<ProgressReport> progress, IList<string> sites, CancellationTokenSource cts) { IsBusy = true; processed = new ConcurrentBag<Website>(); progressCount = 0; totalCount = sites.Count; // 限制并行度为8 var semaphore = new SemaphoreSlim(8); try { var tasks = sites.Select(async site => { await semaphore.WaitAsync(cts.Token); try { var dataBytes = await client.Value.GetByteArrayAsync(site, cts.Token); ReportProgress(progress, dataBytes, site, null); } catch (Exception ex) { ReportProgress(progress, null, site, ex); } finally { semaphore.Release(); } }); await Task.WhenAll(tasks); } // To Debug / Log catch (TaskCanceledException) { Debug.Print("The operation was canceled"); } finally { IsBusy = false; } return processed.ToList(); } private static void ReportProgress(IProgress<ProgressReport> progress, byte[]? data, string site, Exception? ex) { lock (syncObj) { progressCount += 1; var percentage = progressCount * 100 / totalCount; Website website = new(site, data, ex is null, ex); processed.Add(website); progress.Report(new ProgressReport(website, percentage)); } } } }
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

