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

使用C#.Net实现文件夹内百万级文件的持续计数

解决ASP.NET实时文件计数更新的方案

一、先说说你原来代码的问题

你之前的方案有几个核心痛点导致无法正常工作:

  • Directory.EnumerateFiles(...).Count()是一次性计算总数,没法返回中间进度,用户自然看不到逐步增长的数字;
  • 后台线程直接操作Session和页面控件是不安全的——ASP.NET的UI元素和Session都是和请求线程绑定的,后台线程不能直接修改这些内容;
  • 服务器端Timer的Tick事件会触发页面回发,导致页面反复刷新,而且回发后原来的线程状态很容易丢失;
  • 用Thread.Abort()终止线程是危险操作,可能引发资源泄漏。

二、重构后的解决方案

我们调整思路,用异步渐进计数+客户端AJAX无刷新更新的方式实现你要的实时效果:

1. 后台代码改造

用线程安全容器存储每个用户的计数进度,用异步任务替代直接创建Thread,同时支持安全终止任务:

using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Configuration;
using System.Web.Services;

public partial class GetCount : System.Web.UI.Page
{
    // 线程安全字典:存储每个用户的当前文件计数(键用SessionID)
    private static readonly ConcurrentDictionary<string, long> _userFileCounts = new ConcurrentDictionary<string, long>();
    // 线程安全字典:存储每个用户的任务取消令牌
    private static readonly ConcurrentDictionary<string, CancellationTokenSource> _userCts = new ConcurrentDictionary<string, CancellationTokenSource>();

    protected void Page_Load(object sender, EventArgs e)
    {
        // 初始化当前用户的计数为0
        if (!_userFileCounts.ContainsKey(Session.SessionID))
        {
            _userFileCounts.TryAdd(Session.SessionID, 0);
        }
    }

    protected void btnStartCount_Click(object sender, EventArgs e)
    {
        string basePath = WebConfigurationManager.AppSettings["Path"];
        string targetPath = Path.Combine(basePath, TextBox1.Text.Trim());
        
        if (!Directory.Exists(targetPath))
        {
            lblCount.Text = "目标路径不存在!";
            return;
        }

        // 如果已有未完成的任务,先取消
        if (_userCts.TryGetValue(Session.SessionID, out var existingCts))
        {
            existingCts.Cancel();
            existingCts.Dispose();
            _userCts.TryRemove(Session.SessionID, out _);
        }

        // 重置当前用户的计数
        _userFileCounts.TryUpdate(Session.SessionID, 0, _userFileCounts[Session.SessionID]);

        // 创建新的取消令牌
        var cts = new CancellationTokenSource();
        _userCts.TryAdd(Session.SessionID, cts);

        // 启动异步任务进行渐进式计数
        Task.Run(() => CountFilesProgressively(targetPath, cts.Token), cts.Token);
    }

    protected void btnStopCount_Click(object sender, EventArgs e)
    {
        if (_userCts.TryGetValue(Session.SessionID, out var cts))
        {
            cts.Cancel();
            cts.Dispose();
            _userCts.TryRemove(Session.SessionID, out _);
            lblCount.Text = "已停止计数";
        }
    }

    // 渐进式文件计数方法
    private void CountFilesProgressively(string path, CancellationToken token)
    {
        long currentCount = 0;
        try
        {
            // 遍历所有文件,逐个累加计数
            foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories))
            {
                // 检查是否需要取消任务
                if (token.IsCancellationRequested)
                {
                    break;
                }
                currentCount++;
                // 更新当前用户的计数进度
                _userFileCounts.TryUpdate(Session.SessionID, currentCount, _userFileCounts[Session.SessionID]);
                // 短暂休眠,避免占用过多CPU资源
                Thread.Sleep(10);
            }
        }
        catch (Exception ex)
        {
            // 异常时标记错误状态
            _userFileCounts.TryUpdate(Session.SessionID, -1, _userFileCounts[Session.SessionID]);
        }
        finally
        {
            // 任务结束后清理取消令牌
            _userCts.TryRemove(Session.SessionID, out _);
        }
    }

    // 供AJAX调用的WebMethod,获取当前计数
    [WebMethod(EnableSession = true)]
    public static string GetCurrentCount()
    {
        string sessionId = System.Web.HttpContext.Current.Session.SessionID;
        if (_userFileCounts.TryGetValue(sessionId, out long count))
        {
            if (count == -1)
            {
                return "计数过程中出现错误";
            }
            return count.ToString();
        }
        return "0";
    }

    // 供AJAX调用的停止任务方法
    [WebMethod(EnableSession = true)]
    public static void StopCurrentTask()
    {
        string sessionId = System.Web.HttpContext.Current.Session.SessionID;
        if (_userCts.TryGetValue(sessionId, out var cts))
        {
            cts.Cancel();
            cts.Dispose();
            _userCts.TryRemove(sessionId, out _);
        }
    }
}

2. 前端页面改造

用客户端定时器+AJAX实现无刷新实时更新计数:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GetCount.aspx.cs" Inherits="YourNamespace.GetCount" %>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>实时文件计数</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <script>
        var updateTimer;
        
        $(function () {
            // 开始计数按钮逻辑
            $("#btnStartCount").click(function () {
                // 清空之前的定时器
                if (updateTimer) clearInterval(updateTimer);
                // 每2秒请求一次最新计数(可调整间隔)
                updateTimer = setInterval(function () {
                    $.ajax({
                        type: "POST",
                        url: "GetCount.aspx/GetCurrentCount",
                        contentType: "application/json; charset=utf-8",
                        dataType: "json",
                        success: function (res) {
                            $("#lblCount").text(`当前文件数:${res.d}`);
                        },
                        error: function () {
                            $("#lblCount").text("获取计数失败");
                        }
                    });
                }, 2000);
            });

            // 停止计数按钮逻辑
            $("#btnStopCount").click(function () {
                clearInterval(updateTimer);
                // 通知后台停止任务
                $.ajax({
                    type: "POST",
                    url: "GetCount.aspx/StopCurrentTask",
                    contentType: "application/json; charset=utf-8"
                });
            });

            // 页面关闭时清理资源
            window.onbeforeunload = function () {
                clearInterval(updateTimer);
                $.ajax({
                    type: "POST",
                    url: "GetCount.aspx/StopCurrentTask",
                    contentType: "application/json; charset=utf-8",
                    async: false
                });
            };
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:TextBox ID="TextBox1" runat="server" PlaceHolder="输入子文件夹路径"></asp:TextBox>
            <asp:Button ID="btnStartCount" runat="server" Text="开始计数" OnClick="btnStartCount_Click" />
            <asp:Button ID="btnStopCount" runat="server" Text="停止计数" OnClick="btnStopCount_Click" />
            <br /><br />
            <asp:Label ID="lblCount" runat="server" Text="当前文件数:0"></asp:Label>
        </div>
    </form>
</body>
</html>

三、关键注意事项

  • 线程安全:用ConcurrentDictionary存储用户进度,避免多线程竞争问题;
  • 任务安全终止:用CancellationTokenSource替代Thread.Abort(),防止资源泄漏;
  • 无刷新体验:客户端定时器+AJAX完全避免了页面回发,实现你想要的实时更新效果;
  • IIS权限:确保IIS应用池的身份拥有目标文件夹的读取权限,否则会抛出访问异常;
  • 应用池回收:如果计数任务耗时极长,可调整IIS应用池的回收策略,或者考虑用Windows服务单独处理这类长时间任务。

内容的提问来源于stack exchange,提问作者polisetty dhana harsha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:53:15