【问题标题】:Handle "A task was canceled" exception处理“任务被取消”异常
【发布时间】:2020-08-05 16:57:43
【问题描述】:

我的 C# 应用程序将文件上传到某些 API,我正在使用多部分请求,即我正在上传文件的 json 字符串和二进制文件,它适用于大多数文件,但对于极少数文件,它会给出异常,我意思是让我们尝试名为 50MB.zip 的文件 我得到了例外:

A task was canceled. :::  ::: System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()

我的代码大致如下:

public async Task<Dictionary<string , string>> Upload(string filePath)
{   
    FileInfo fi = new FileInfo(FilePath);
    string jsonString="some json string";
    byte[] fileContents=File.ReadAllBytes(fi.FullName);
    Uri webService = new Uri(url);
    HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post , webService);
    requestMessage.Method = HttpMethod.Post;
    requestMessage.Headers.Add("Authorization" , "MyKey1234");
    const string boundry = "------------------My-Boundary";
    MultipartFormDataContent multiPartContent = new MultipartFormDataContent(boundry);
    ByteArrayContent byteArrayContent = new ByteArrayContent(fileContents);
    multiPartContent.Add(byteArrayContent);
    requestMessage.Content = multiPartContent;
    HttpClient httpClient = new HttpClient();
    HttpResponseMessage httpResponse = await httpClient.SendAsync(requestMessage , HttpCompletionOption.ResponseContentRead , CancellationToken.None);
    //exception in this line ^
    return new Dictionary<string , string>();
}

调用者:

myDictionary = await Upload(filePath);

控制台应用的结构如下:

class Program
{
    static void Main(string[] args)
    {
        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        new MyClass().Start();
    }
}

MyClass:里面

public async void Start()
{
    myDictionary = await Upload(filePath);
}

我想我没有正确使用异步,你能看到我错过了什么吗?有什么想法吗?

【问题讨论】:

  • 您是否处理掉了您所有的IDisposable 对象?
  • 首先,不要每次都实例化新的HttpClient 对象。在大多数情况下,您应该只使用一个一遍又一遍。其次,这个异常通常是因为请求超时而发生的。尝试在您的 HttpClient 上设置更高的超时限制
  • 此外,如果您将高级构建选项设置为使用最新的 C# 次要版本,则可以使用 static async Task Main 方法,因此您可以使用 await 您的 MainAsync() 任务而不是使用阻塞Wait()
  • 使用调试器找出这个任务是如何被取消的。在 VS2017 中,使用 Debug > Windows > Exception Settings 并勾选 CLR exceptions 复选框。如果这导致调试器过于频繁地中断,那么您可以通过仅在 OperationCanceledException 上停止来使其更具体

标签: c# async-await


【解决方案1】:

我 99% 确定此错误是由于超时,或者您实际上并未在 MainAsync 中等待您的 Start 方法所致

我已经解决了以下代码中的超时问题以及其他一些小更改,这些更改不一定能回答您的问题,但希望对您有所帮助

class Program
{
    private static HttpClient httpClient;

    static void Main(string[] args)
    {
        httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("your base url");
        // add any default headers here also
        httpClient.Timeout = new TimeSpan(0, 2, 0); // 2 minute timeout

        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        await new MyClass(httpClient).StartAsync();
    }
}

我在这里所做的是将HttpClient 从您的Upload() 方法中移出,因为该类旨在被多次重用。我已将httpClient 对象传递给MyClass 的构造函数,您将在下一个代码sn-p 中看到它。

我还将 MainAsync() 更改为 await StartAsync(从 Start 重命名为 StartAsync,因为它是异步方法后缀的约定),因为在您的原始代码中 MainAsync() 实际上并没有等待任何东西

正如我在 cmets 中提到的,如果您将 Main 更改为 static async Task Main,则可以将 MainAsync().Wait() 更改为 await MainAsync(),这需要您将构建语言更改为 C# 7.1 或更高版本

public class MyClass
{
    private Dictionary<string, string> myDictionary;
    private readonly HttpClient httpClient;

    public MyClass(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public async Task StartAsync()
    {
        myDictionary = await UploadAsync("some file path");
    }

    public async Task<Dictionary<string, string>> UploadAsync(string filePath)
    {
        byte[] fileContents;
        using (FileStream stream = File.Open(filePath, FileMode.Open))
        {
            fileContents = new byte[stream.Length];
            await stream.ReadAsync(fileContents, 0, (int)stream.Length);
        }

        HttpRequestMessage requestMessage = new HttpRequestMessage();
        // your request stuff here

        HttpResponseMessage httpResponse = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, CancellationToken.None);

        // parse response and return the dictionary
    }
}

MyClass我做了以下改动

在类的构造函数中添加了HttpClient 参数,因此我们可以将全局 HttpClient 对象传递给该类以供其重用(这就是我们在MainAsync() 中所做的)

如前所述,我已将 StartUpload 重命名为 StartAsyncUploadAsync,因为使用 Async 为异步方法添加后缀是一种很好的做法

Startvoid 更改为 Task because you should only use async void for event handlers

我将读取文件的方式也更改为异步,因为使用 async 方法然后阻塞 CPU 等待 File.ReadAllBytes 完成似乎很浪费。您应该尽可能使用 async/await 进行 I/O。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多