【问题标题】:How to await for multiple tasks when using lambda exp on the event handler在事件处理程序上使用 lambda exp 时如何等待多个任务
【发布时间】:2014-02-19 21:25:31
【问题描述】:

我有 2 个类 Main.cs 和 Processing.cs(简称 P 和 M)类 M 通过一个 html 链接调用 P,P 在 tern 下载,转换为 Base64,重命名并保存文件,然后返回一个字符串回到 M,现在我需要 M 等到所有这些都完成才能继续,但我一直没能。

我在事件处理程序中使用了 lambda 表达式,以便能够在该函数中执行所有操作,而不是为事件触发器使用单独的函数,因此我可以使用 Base64 转换文件返回字符串,但它只是返回空字符串,不要等到它被分配。

我认为 taskA.Wait() 调用会使其等待所有处理完成,但事实并非如此

如果有人有任何想法,我将不胜感激。

来自 Main.cs 的调用是这样的:

Processing processing = new processing();
String _link = “http://www.something.com”;
var ResultBase64_Val = processing.FileToBase64(_link).Result;

在 Processing.cs 中的函数是:

public async Task<String> FileToBase64(String filePath)
{
    String convertedFile = "";

    WebClient client = new WebClient();
    Task taskA = Task.Factory.StartNew(() => client.OpenReadCompleted += async (object sender, OpenReadCompletedEventArgs e) =>
     {   
           byte[] buffer = new byte[e.Result.Length];
           buffer = new byte[e.Result.Length];
           await e.Result.ReadAsync(buffer, 0, buffer.Length);
           convertedFile = Convert.ToBase64String(buffer);
     });
     client.OpenReadAsync(new Uri(filePath));
     taskA.Wait();

     return convertedFile;           
    }

谢谢, 鲍勃

【问题讨论】:

  • 您使用的是什么版本的 .NET?
  • @DavidSchwartz 我正在使用随 Windows 8.1 安装的 .NET Framework 4.5.1 关于您之前的评论,我认为任务意味着块中的所有内容,而不仅仅是 OpenReadComplete 事件。我选择了 async WebClient 方法,因为它是 wp8 应用程序和移动网络并不总是稳定的,所以 async 似乎更安全,对吗?
  • 考虑使用System.Security.Cryptography.ToBase64Transform 而不是Convert.ToBase64String(参见ToBase64Transform on MSDN for examples)。它将让您直接使用流,而不是在内存中创建字节数组。或者,如果响应大于您根据预期使用情况决定的某个上限,则抛出异常。
  • @DavidSchwartz 这不能解决让任务等到 d/l 完成并转换的问题......除了这些是图像之外,我认为它会有所作为吗?跨度>
  • 这就是为什么我添加评论而不是把它放在答案中的原因:) 我会说使用 Stream 而不是巨大的字节数组甚至是 more 重要如果您正在下载二进制数据(图像)。如果您将数据从请求流式传输到端点(如文件),您只需将两个流的缓冲区保存在内存中(可能〜 4KB)而不是整个响应(网络上的图像通常在 100 个KB)。

标签: c# asynchronous windows-phone-8 lambda async-await


【解决方案1】:

您的代码的问题在于,以Task.Factory.StartNew 开始的任务会立即完成,远在OpenReadCompleted 将来某个时间被触发之前。也就是说,用Task.RunTask.Factory.StartNew 包装像OpenReadAsync 这样的自然异步API 无论如何都是一个坏主意。即使您以某种方式等待事件,或者使用同步OpenRead,您也会浪费一个池线程。

为此有新的WebClient.OpenReadTaskAsync 方法:

public async Task<String> FileToBase64(String filePath)
{
    using (var client = new WebClient())
    using (var stream = await client.OpenReadTaskAsync(new Uri(filePath)))
    {
        // use stream.ReadAsync
    }
}

我也推荐HttpClient而不是WebClient,前者支持多个HTTP请求并行:

using System.Net.Http;

// ...

public async Task<String> FileToBase64(String filePath)
{
    using (var client = new HttpClient())
    using (var response = await client.GetAsync(filePath))
    using (var stream = await response.Content.ReadAsStreamAsync())
    {
        return string.Empty;
        // use stream.ReadAsync
    }
}

【讨论】:

  • 我收到错误Error 3 'System.Net.WebClient' does not contain a definition for 'OpenReadTaskAsync' and no extension method 'OpenReadTaskAsync' accepting a first argument of type 'System.Net.WebClient' could be found (are you missing a using directive or an assembly reference?) 我认为这是 .Net Ver 问题,我使用的是 Ver 4.5.1
  • @BobMachine,从 .NET 4.5 开始肯定是 there。确保在源文件顶部有 using System.Net; 语句。
  • @BobMachine 确保您的项目设置为使用 .NET 4.5 构建(右键单击项目,转到 Application 选项卡,更改 Target framework .NET Framework 4.5)
  • @Noseratio 这是一个 windows phone 8 应用程序,这就是目标下拉菜单中显示的内容。这可能是原因吗?这很奇怪,因为我什至在第一个 using 上遇到了错误...而且我确实有 using System.Net
  • @Noseratio 谢谢你它工作得很好,这是我的错误,我没有等待电话,我很傻。我之前期待一个字符串,现在它是 Task 谢谢你的帮助:)
【解决方案2】:

如果你想使用 WebClient,并且你想从方法中返回结果,你需要使用 TaskCompletionSource 并正常订阅事件

public Task<String> FileToBase64(String filePath)
{
    TaskCompletionSource<string> completion = new TaskCompletionSource<string>();
    String convertedFile = "";

    WebClient client = new WebClient();
    client.OpenReadCompleted += (s, e) =>
    {
        TextReader reader = new StreamReader(e.Result);
        string result = reader.ReadToEnd();
        completion.SetResult(result);
    };
    client.OpenReadAsync(new Uri(filePath));

    return completion.Task;
}

鉴于此答案,我建议使用 HttpClient 而不是 WebClient

【讨论】:

  • 您的示例代码不起作用,我没有收到任何错误,但这不是来自 URL 的图像文件。我尝试将TaskCompletionSource 与我拥有的 b4 一起使用,但它也不起作用。
  • 返回什么?结果的价值是什么?
  • 对不起,它确实有效,这是我的错误,我没有等待通话,我忘了在它期待一个字符串之前把它放好,这会导致错误。它可以正确下载,但Convert.ToBase64() 给了我一个奇怪的结果,只是一系列AAAA..
【解决方案3】:

Noseratio answerShawn Kendrot answer 都应该可以工作。但我会尝试使用其他方法 - WebRequest

首先,我必须将我的 WebRequest 方法扩展为 GetRequestStreamAsync() - WP 缺少此方法:

public static class Extensions
{
  public static Task<Stream> GetRequestStreamAsync(this WebRequest webRequest)
  {
    TaskCompletionSource<Stream> taskComplete = new TaskCompletionSource<Stream>();
    webRequest.BeginGetRequestStream(arg =>
    {
        try
        {
            Stream requestStream = webRequest.EndGetRequestStream(arg);
            taskComplete.TrySetResult(requestStream);
        }
        catch (Exception ex) { taskComplete.SetException(ex); }
    }, webRequest);
    return taskComplete.Task;
  }
}

然后我会把你的任务转换成这样的:

public async Task<string> FileToBase64(string filePath)
{
    try
    {
       WebRequest request = WebRequest.Create(new Uri(filePath));
       if (request != null)
       {
           using (Stream resopnse = await request.GetRequestStreamAsync())
           using (MemoryStream temp = new MemoryStream())
           {
               const int BUFFER_SIZE = 1024;
               byte[] buf = new byte[BUFFER_SIZE];

               int bytesread = 0;
               while ((bytesread = await resopnse.ReadAsync(buf, 0, BUFFER_SIZE)) > 0)
                     temp.Write(buf, 0, bytesread);

               return Convert.ToBase64String(temp.ToArray());
            }
        }
        return String.Empty;
   }
   catch { return String.Empty; }
}

也许这会有所帮助。

【讨论】:

  • 谢谢,你是正确的两种解决方案都有效,但在 Shawn Kendrot 的Convert.ToBase64() 上得到了一个奇怪的结果,但 Noseratio 的解决方案运行良好。我尝试了您的建议,但它给了我一个错误Extension methods must be defined in a non-generic static class 我尝试创建一个单独的类但错误仍然存​​在。
  • @BobMachine 欢迎您。我已经添加了它在类中的外观,以防其他人需要它。如果你能测试它并说它是否有效,那就太好了——我还没有尝试过。
  • 对不起昨天我太累了我崩溃了......我测试了它,能够编译但在MemoryStream init 之后立即出现异常,这是错误A first chance exception of type 'System.Net.ProtocolViolationException' occurred in System.Windows.ni.dll An exception of type 'System.Net.ProtocolViolationException' occurred in System.Windows.ni.dll and wasn't handled before a managed/native boundary 再次感谢为您提供帮助:)
  • @BobMachine 感谢您的信息。然后它应该针对您的目的进行微调,或者可能通过 WebResponse 完成。但是不再需要,因为您已经有了工作解决方案:)。再次感谢您的回复。
  • 欢迎您 :) 希望见到您,您真的很酷。我有时也很挣扎,寻找 w8 的信息,但与 wp8 不兼容。
【解决方案4】:

首先,不要将Task.Factory.StartNewasync 方法一起使用。 Stephen ToubStephen Clearly 已经解释了原因。

其次,如果您使用的是async-await,那么您可以将WebClient class~TaskAsync 方法与Microsoft Async NuGet package 结合使用——假设您说它是用于Windows电话 8

附带说明,您应该始终使用 Async(如果不可能,则使用 TaskAsync)为您的异步方法添加后缀。

鉴于此,您的代码变为:

public async Task<String> FileToBase64Async(String filePath)
{
    String convertedFile = "";

    var client = new WebClient();

    Task taskA = Task.Factory.StartNew(() => client.OpenReadCompleted += async (object sender, OpenReadCompletedEventArgs e) =>
    {
        var buffer = await webclient.DownloadDataTaskAsync(filePath);
        convertedFile = Convert.ToBase64String(buffer);

        return convertedFile;           
    }
}

但是您可以在Microsoft HTTP Client Libraries NuGet package 上找到新的HttpClient class,为您提供更好的服务。

最后,你should not block on async code in the UI thread

【讨论】:

  • 感谢您的提示,Noseratio 的建议效果很好,我只是忘记等待函数调用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-20
  • 2015-04-29
  • 1970-01-01
相关资源
最近更新 更多