You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

取消服务操作后Blazor InputFile的OnChange事件无法触发

问题原因

  1. CancellationTokenSource 复用问题:你在组件中仅初始化了一个CancellationTokenSource实例,取消操作后该实例进入已取消状态,无法再次复用。后续上传文件时,传入的令牌已经处于取消状态,导致服务方法刚执行就抛出OperationCanceledException,表现为无响应,让你误以为InputFile的OnChange事件未触发。
  2. 资源未正确释放:读取文件时未用using包裹StreamReader,可能导致资源泄漏,影响组件后续行为。
  3. 取消逻辑不完整:异步读取文件的方法未传入取消令牌,导致取消操作无法立即中断文件读取流程。

修复方案

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,避免状态不一致。

验证修复

  1. 取消当前任务后,InputFile组件可正常触发OnChange事件;
  2. 每次上传都会创建新的取消令牌,不会受之前取消操作影响;
  3. 文件读取流程能立即响应取消请求,资源释放更彻底。

内容的提问来源于stack exchange,提问作者lenny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:59:52