【问题标题】:Why Task.Run not processing all lines on Task?为什么 Task.Run 不处理 Task 上的所有行?
【发布时间】:2021-04-12 18:31:07
【问题描述】:

我在客户端使用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 字符串到图像的图像转换。

这是我的课程,它没有通过 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);
                        }
                    });
                }
         }



    

【问题讨论】:

  • it does not pass the 1st statment on Task.Run 你是怎么找到的?为什么你需要一个单独的线程来处理已经 async 的代码?你想解决什么问题?请使用调试详细信息更新问题。
  • 猜测 uwpandroid 标记是冲突的,因为 UWP 是关于 Windows 而不是 Android。
  • 我通过调试所有行发现它没有通过语句 byte[] bytes = Convert.FromBase64String(s);
  • 关于 android...是的,它可能不合适,但我正在从事 Uno Project,所以它也可能适用于那里。
  • 修正了..抱歉是错字

标签: c# uwp task


【解决方案1】:

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

网页部分

  • 避免在不是事件处理程序的方法中使用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;
    }
}

解码器的代码基于this answer

如果延迟,请尝试使用Task.Run 包装 2 解码器的行。仅当它会滞后时。

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

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

【讨论】:

  • 非常感谢,在这些更改之后它可以工作了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-04
  • 1970-01-01
  • 2018-06-27
  • 2019-07-30
  • 1970-01-01
  • 2016-09-24
  • 1970-01-01
相关资源
最近更新 更多