异步处理传入的 FileStream

Process incoming FileStream asynchronously

我正在读取用户上传的文件,它正在同步工作。我需要更改它以便立即向用户发送“已收到”警报,然后异步读取文件,同时用户会定期轮询以查看读取是否完成。

这是我的代码现在的样子:

public FileUpload SaveFile(Stream stream)
{
        FileUpload uploadObj = //instantiate the return obj

        var task = Task.Run(async () => await ProcessFileAsync(stream));
        
        return upload;
}

public async Task ProcessFileAsync(Stream stream)
{
        StreamReader file = new StreamReader(stream);
        CsvReader csv = new CsvReader(file, CultureInfo.InvariantCulture);
        
        while (await csv.ReadAsync())
        {
           //read the file
        }
}

我遇到的问题是,当我调用 csv.ReadAsync() 方法时,Stream 对象已被释放。当我希望 SaveFile() 方法向用户 return 一个值时如何访问 Stream,但是 returning 的行为处理了 Stream 对象?

如果环境允许,您需要执行此操作:

var result = task.Result;
//do stuff

...或

public Task<FileUpload> SaveFile(Stream stream)
{
    var uploadObj = //instantiate the return obj

    await ProcessFileAsync(stream);
    
    return uploadObj;
}

如果您走那条路,请参阅此处以获得关于 fire-and-forget 的详尽讨论: Web Api - Fire and Forget

这里的要点是您在 ASP.NET 的约束下工作,它抽象出了很多底层 HTTP 内容。

当您说要异步处理 user-uploaded 文件时,您想要跳出使用 HTTP 和 ASP.NET 处理事情的正常顺序。您会看到,当客户端发送带有 body(文件)的请求时,服务器会收到请求 headers 并启动 ASP.NET 以告诉您的应用程序代码有一个新的请求传入.

此时它甚至还没有(完全)读取请求 body。这就是为什么你得到一个 Stream 来处理请求,而不是一个字符串或文件名——数据还没有到达服务器!只是请求 headers,将请求通知 Web 服务器。

如果您return a response at that point,就所有 HTTP 和 ASP.NET 而言,您已完成请求,无法继续阅读其 body。

现在您要做的是读取请求body(文件),并在向客户端发送响应后处理。你可以这样做,但是你仍然需要阅读请求 body - 因为如果你在阅读请求之前 return 来自你的操作方法的东西,框架会认为你已经完成了它并处理请求流。这就是导致您出现异常的原因。

如果您使用字符串、模型绑定或任何涉及框架读取请求的内容 body,那么是的,您的代码只会在 body 被读取后执行.

short-term 解决方案似乎可以让您继续前进,是 read the request stream into a stream that you own,而不是框架:

var myStream = new MemoryStream();
await stream.CopyTo(myStream);
Task.Run(async () => await ProcessFileAsync(myStream));

现在您已经读取了整个请求 body 并将其保存在内存中,因此 ASP.NET 可以安全地处理请求流并向客户端发送响应。

但是不要这样做。从控制器启动 fire-and-forget 任务是个坏主意。将上传的文件保存在内存中是个坏主意。

实际上应该做什么,如果你还想这样做的话out-of-band:

  • 将传入文件另存为服务器上的实际临时文件
  • 使用标识符(临时生成的文件名,例如 GUID)向客户端发送响应
  • 公开一个端点,客户端可以使用所述 GUID 来请求状态
  • 让后台进程不断扫描目录以查找新上传的文件并进行处理

对于后者,您可以 hosted services 或 third-party 像 Hangfire 这样的工具。