【问题标题】:Process incoming FileStream asynchronously异步处理传入的 FileStream
【发布时间】:2020-08-13 22:13:30
【问题描述】:

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

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

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() 方法向用户返回一个值,但返回的行为会释放 Stream 对象时,如何访问 Stream?

【问题讨论】:

  • 如何获取您的信息流?是网络应用吗?
  • 是的,用户正在通过 webapp 上传,然后将其发送到我的服务器 API,然后该 API 将流传递给我的方法。
  • 目标框架.NET Core 2.1
  • 我认为您必须将流复制到 MemoryStream 以将其保存在内存中并将其交给不同的线程。
  • HTTP 是一种请求-响应协议。客户端发送请求,服务器读取并返回响应。以该顺序。在标准 HTTP 文件上传的情况下,文件本身就是请求;在阅读之前,您无法发回响应。您可以通过 AJAX 来模拟这种事情(请参阅this question and answer),但它需要多个请求-响应对。您无法仅从服务器端做到这一点。

标签: c# asynchronous streamreader asp.net-core-2.1


【解决方案1】:

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

当您说要异步处理用户上传的文件时,您希望跳出使用 HTTP 和 ASP.NET 的正常处理顺序。您会看到,当客户端发送带有正文(文件)的请求时,服务器会接收请求标头并启动 ASP.NET 以告诉您的应用程序代码有新请求传入。

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

如果您 return a response at that point,对于所有 HTTP 和 ASP.NET 的关心,您已经完成了请求,并且您无法继续阅读它的正文。

现在您要做的是读取请求正文(文件),并处理向客户端发送响应之后。你可以这样做,但是你仍然必须阅读请求正文——因为如果你在阅读请求之前从你的操作方法中返回一些东西,框架会认为你已经完成了它并处理请求流。这就是导致您的异常的原因。

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

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

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

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

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

实际上应该做什么,如果你仍然想在带外做这个:

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

对于后者,您可以 hosted services 或 Hangfire 等第三方工具。

【讨论】:

    【解决方案2】:

    如果环境允许,您需要这样做:

    var result = task.Result;
    //do stuff
    

    ...或

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

    如果您走这条路,请参阅此处,了解关于即发即弃的全面讨论: Web Api - Fire and Forget

    【讨论】:

    • 那么在第二个例子中,await 命令不只是同步执行吗?如果 SaveFile() 等到 ProcessFileAsync() 在返回 FileUpload 对象之前完成,我也可以在 SaveFile() 方法中处理文件。用户需要在文件被读取之前接收 FileUpload 对象。
    • @BrandonMiller - 不。Await 异步操作,但它会等到进程完成。异步只是意味着它允许程序的其他部分在异步代码“等待”长时间运行的过程的结果时运行。您遇到的问题是,默认情况下,一旦您返回某些内容,请求就会停止,因此文件操作基本上会按照您的操作方式被取消(因此为什么要处理它)。请参阅此处以了解 ASP.NET stackoverflow.com/questions/36335345/web-api-fire-and-forget 中关于火灾和遗忘的讨论
    • 所以我尝试了这种方式并且没有失败,每次程序执行 ProcessFile 方法完成,然后将uploadObj返回给用户。这是因为 SaveFileMethod 在返回 uploadObj 之前正在等待 ProcessFile 方法的结果,因此我需要在处理该 FileStream 时或之前找到一种方法将 uploadObj 返回给用户。
    • @BrandonMiller - 对。我在评论中解释了原因,并提供了“即发即弃”线程的链接,该线程讨论了在文件上传之前返回值的方法,但我更新了我的答案以包含它。
    猜你喜欢
    • 2020-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    • 2019-08-29
    相关资源
    最近更新 更多