取消服务操作后Blazor InputFile的OnChange事件无法触发
问题原因
- CancellationTokenSource 复用问题:你在组件中仅初始化了一个
CancellationTokenSource实例,取消操作后该实例进入已取消状态,无法再次复用。后续上传文件时,传入的令牌已经处于取消状态,导致服务方法刚执行就抛出OperationCanceledException,表现为无响应,让你误以为InputFile的OnChange事件未触发。 - 资源未正确释放:读取文件时未用
using包裹StreamReader,可能导致资源泄漏,影响组件后续行为。 - 取消逻辑不完整:异步读取文件的方法未传入取消令牌,导致取消操作无法立即中断文件读取流程。
修复方案
1. 重置 CancellationTokenSource
每次开始新上传任务时创建新的CancellationTokenSource,取消或任务结束后释放资源:
修改Index.razor的@code部分:
@code { private IOrderedEnumerable<KeyValuePair<string, int>>? wordCounts = null; private CancellationTokenSource? cancellationTokenSource; // 改为可空类型 private int progressPercentage; protected override async Task OnInitializedAsync() { Service.ProgressChanged += ServiceProgressChanged; } private async void InputFileChanged(InputFileChangeEventArgs e) { if (Service.Running) { return; } // 为新任务创建新的取消令牌源 cancellationTokenSource = new CancellationTokenSource(); IReadOnlyList<IBrowserFile> files = e.GetMultipleFiles(); try { wordCounts = await Service.GetWordCounts(files, cancellationTokenSource.Token); progressPercentage = 100; } catch (OperationCanceledException) { wordCounts = null; progressPercentage = 0; } finally { // 释放资源,避免内存泄漏 cancellationTokenSource?.Dispose(); cancellationTokenSource = null; } StateHasChanged(); } private void ServiceProgressChanged(object? sender, ProgressChangedEventArgs args) { InvokeAsync(() => { progressPercentage = args.ProgressPercentage; StateHasChanged(); }); } private void ButtonCancelClick(EventArgs e) { // 仅当令牌源存在时执行取消 cancellationTokenSource?.Cancel(); } private void ButtonResetClick(EventArgs e) { wordCounts = null; } }
2. 完善服务层取消逻辑与资源管理
修改FileCounterService.cs,确保取消能立即生效,同时正确释放资源:
using Microsoft.AspNetCore.Components.Forms; using System.ComponentModel; using System.IO; using System.Linq; namespace FileWordCounter.Service { public class FileCounterService { public bool Running { get; private set; } public event ProgressChangedEventHandler? ProgressChanged; private long totalBytes; private long progressedBytes; public async Task<IOrderedEnumerable<KeyValuePair<string, int>>?> GetWordCounts( IEnumerable<IBrowserFile> files, CancellationToken cancellationToken) { try { Running = true; var wordCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); // 可选:忽略大小写统计单词 totalBytes = files.Sum(x => x.Size); progressedBytes = 0; foreach (var file in files) { // 修正maxFileSize为1GB(原代码写的是1TB) const long maxFileSize = 1024L * 1024 * 1024; // 使用using自动释放StreamReader资源 using var reader = new StreamReader(file.OpenReadStream(maxFileSize)); string? line = await reader.ReadLineAsync(cancellationToken); while (line != null) { // 提前检查取消请求,快速响应 cancellationToken.ThrowIfCancellationRequested(); if (string.IsNullOrWhiteSpace(line)) { line = await reader.ReadLineAsync(cancellationToken); continue; } // 分割单词时移除空条目,减少无效处理 var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var word in words) { cancellationToken.ThrowIfCancellationRequested(); if (wordCounts.ContainsKey(word)) { wordCounts[word]++; } else { wordCounts.Add(word, 1); } } progressedBytes += line.Length + 2; var progressPercentage = (int)(progressedBytes * 100f / totalBytes); ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(progressPercentage, null)); line = await reader.ReadLineAsync(cancellationToken); } } return wordCounts.OrderByDescending(pair => pair.Value); } finally { // 无论任务成功还是取消,都重置Running状态 Running = false; } } public void Stop() { Running = false; } } }
3. 额外优化点
- 单词统计添加
StringComparer.OrdinalIgnoreCase,实现大小写不敏感统计(可选); - 修正
maxFileSize为1GB,与注释保持一致; - 使用
string.IsNullOrWhiteSpace替代空字符串判断,更严谨; - 在
finally块中统一设置Running=false,避免状态不一致。
验证修复
- 取消当前任务后,
InputFile组件可正常触发OnChange事件; - 每次上传都会创建新的取消令牌,不会受之前取消操作影响;
- 文件读取流程能立即响应取消请求,资源释放更彻底。
内容的提问来源于stack exchange,提问作者lenny
相关产品推荐
相关产品推荐

