Search code examples
c#asp.netasp.net-coredevexpresstask-parallel-library

Http.IMiddleware Do not create tasks without passing a TaskScheduler


(.Net 3.1, Visual Studio 2019)

In the DevExpress code example: https://github.com/DevExpress-Examples/blazor-server-dxdatagrid-export/blob/19.2.2%2B/CS/DxDataGridExportingWithReports/Helpers/ExportMiddleware.cs, the following http middleware code got the warning of

Do not create tasks without passing a TaskScheduler ...

What's the proper way to rewrite the code to start the new task?

public class ExportMiddleware : IMiddleware
{
    ......

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
       ......

        _ = await new TaskFactory().StartNew(() => // warning: Do not create tasks without passing a TaskScheduler
        {
            report.CreateDocument();
            using (MemoryStream fs = new MemoryStream())
            {
                if (format == pdf)
                    report.ExportToPdf(fs);
                else if (format == xlsx)
                    report.ExportToXlsx(fs);
                else if (format == docx)
                    report.ExportToDocx(fs);
                context.Response.Clear();
                context.Response.Headers.Append("Content-Type", "application/" + format);
                context.Response.Headers.Append("Content-Transfer-Encoding", "binary");
                context.Response.Headers.Append("Content-Disposition", "attachment; filename=ExportedDocument." + format);
                context.Response.Body.WriteAsync(fs.ToArray(), 0, fs.ToArray().Length);
                return context.Response.CompleteAsync();
            }
        });

Solution

  • Technically, to answer the actual question, the code should use Task.Run instead of StartNew:

    _ = Task.Run(() =>
    

    However, this is a phenomenally bad idea. Not only is this doing fire-and-forget, it will make use of context.Response at some random time in the future. And it'll start a write to the response stream and then complete the stream before the write completes. It's just completely broken.

    I believe a more appropriate solution would be to just remove the factory/start/run completely and use await where necessary:

    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
      ......
      report.CreateDocument();
      using (MemoryStream fs = new MemoryStream())
      {
        if (format == pdf)
          report.ExportToPdf(fs);
        else if (format == xlsx)
          report.ExportToXlsx(fs);
        else if (format == docx)
          report.ExportToDocx(fs);
        context.Response.Clear();
        context.Response.Headers.Append("Content-Type", "application/" + format);
        context.Response.Headers.Append("Content-Transfer-Encoding", "binary");
        context.Response.Headers.Append("Content-Disposition", "attachment; filename=ExportedDocument." + format);
        await context.Response.Body.WriteAsync(fs.ToArray(), 0, fs.ToArray().Length);
        await context.Response.CompleteAsync();
      }
    }