为什么 Task.Run 不处理任务中的所有行?

Why Task.Run not processing all lines on Task?

我正在使用 WebApi 在客户端反序列化对象, 包含一些轻量级图像,代码如下:

    private void Button_Click(object sender, object e)
    {
        LoadApi();
    }

    private async void LoadApi()
    {
        using (var client = new HttpClient())
        {
            var responseMessage = await client.GetAsync("http://" + 
            TxtIP.Text + "/api/prod");

            if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
            {
                List<ClsProd> lstData = new List<ClsProd>();
                var jsonResponse = await 
                responseMessage.Content.ReadAsStringAsync();

                if (jsonResponse != null)
                {
                    lstData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ClsProd>>(jsonResponse);
                }                        
                ListView1.ItemsSource = lstData;
            }
        }
    }

我的 ClsProd 看起来 从 Web 获取所有数据 Api 是:

public class ClsProd : System.ComponentModel.INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public int IAuto { get; set; }
    public int IDevc { get; set; }
    public string SName { get; set; }
    public string SImax { get; set; }
    public ImageSource ImgPg { get; set; }

    public ClsProd(int auto, int devc, string name, string imax)
    {
        IAuto = auto;
        IDevc = devc;
        SName = name;
        SImax = imax;


        ClsImgBase64 CImg = new ClsImgBase64();
        CImg.EvtResult += CImg_EvtResult;
        CImg.Start(imax);
    }

    private void CImg_EvtResult(ImageSource e)
    {
        ImgPg = e;
        NotifyPropertyChanged("ImgPg");
    }
}

所有数据均已正确获取并显示在列表中,包括 string SImax 是图像编码为 Base64 字符串。唯一的问题是没有发生从 base64 字符串到图像的图像转换。

这是我的 class 它没有通过 Task.Run 上的第一个声明,请帮我找出问题所在。从 async void 调用时,同样的功能也有效。

        public class ClsImgBase64
        {
                public event Action<ImageSource> EvtResult;
              
                public ClsImgBase64()
                {
    
                }
    
                public void Start(string s)
                {
                    System.Threading.Tasks.Task.Run(async () => 
                    {
                        //read stream
                        byte[] bytes = Convert.FromBase64String(s);
    
                        var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
                        //decode image
                        //var decoder = await BitmapDecoder.CreateAsync(image);
                        image.Seek(0);
                        //create bitmap
                        var output = new WriteableBitmap(1, 1);
                        await output.SetSourceAsync(image);
    
                        if (EvtResult != null)
                        {
                            EvtResult(output);
                        }
                    });
                }
         }



    

根据 async void 可能会抛出异常,该异常已丢失且未显示,因为未等待执行代码。让我们修复它。

Web 部件

  • 避免在不是事件处理程序的方法中使用 async void,也在 async void 方法中处理所有可能的异常
  • HttpClient 旨在为每个应用程序而不是每次使用实例化一次
  • HttpResponseMessageIDisposable
private async void Button_Click(object sender, object e)
{
    try
    {
        await LoadDataAsync();
    }
    catch (Exception ex)
    {
        // show ex.Message here in UI or log it
    }
}

private static readonly HttpClient _client = new HttpClient();

private async Task LoadDataAsync()
{
    using var response = await _client.GetAsync($"http://{TxtIP.Text}/api/prod");
    string json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
    List<ClsProd> data = JsonConvert.DeserializeObject<List<ClsProd>>(json);
    ListView1.ItemsSource = data;
    await DecodeAllImagesAsync(data);
}

// decoding all at once asynchronously, see implementation below
private Task DecodeAllImagesAsync(List<ClsProd> data)
{
    return Task.WhenAll(data.Select(item => item.DecodeImageAsync()).ToArray());
}

Consider using System.Text.Json 去序列化而不是旧的 Newtonsoft.Json。它将允许将 response.Content 反序列化为 Stream,速度更快,内存消耗更少,例如:

using var stream = await response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync();
List<ClsProd> data = await JsonSerializer.DeserealizeAsync<List<ClsProd>>(stream);

数据部分

在代码开头使用 using 指令附加命名空间,这将有助于避免在代码中显式重复命名空间

using System.ComponentModel;

它可以写成 INotifyPropertyChanged 而不是 System.ComponentModel.INotifyPropertyChanged。我将在下面删除内联命名空间。

  • 不要从构造函数开始长运行 作业,这是不可预测的行为,因为构造函数必须始终成功。稍后开始加载图像。构造函数也不能等待异步任务。单独的方法即可。
public class ClsProd : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private ImageSource _imgPg;

    public int IAuto { get; set; }
    public int IDevc { get; set; }
    public string SName { get; set; }
    public string SImax { get; set; }
    public ImageSource ImgPg
    {
        get => _imgPg;
        set
        {
            _imgPg = value;
            NotifyPropertyChanged();
        }
    }

    public ClsProd(int auto, int devc, string name, string imax)
    {
        IAuto = auto;
        IDevc = devc;
        SName = name;
        SImax = imax;
    }

    public async Task DecodeImageAsync()
    {
        ImgPg = await ClsImgBase64.DecodeAsync(SImax);
    }
}

解码器

因为现在它是可等待的并且不需要回调,解码方法不与实例数据交互。所以,它可以是 static.

public static class ClsImgBase64
{
    public static async Task<ImageSource> DecodeAsync(string base64)
    {
        byte[] bytes = Convert.FromBase64String(base64);
        using var stream = bytes.AsBuffer().AsStream().AsRandomAccessStream();
        // stream.Seek(0); // not sure if it needed
        var decoder = await BitmapDecoder.CreateAsync(stream);
        var pixelData = await decoder.GetPixelDataAsync();
        var pixelArray = pixelData.DetachPixelData();
        var bitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
        await bitmap.PixelBuffer.AsStream().WriteAsync(pixelArray, 0, pixelArray.Length);
        return bitmap;
    }
}

基于的解码器代码。

如果会卡顿,请尝试用 Task.Run 包裹 2 行解码器。除非它会滞后。

using var stream = await Task.Run(() =>
{
    byte[] bytes = Convert.FromBase64String(base64);
    return bytes.AsBuffer().AsStream().AsRandomAccessStream();
});

最后:给类、方法和其他东西更清晰的名称,这将使代码易于维护。