使用HttpClient的异步文件下载时,线程问题 [英] Threading issues when using HttpClient for asynchronous file downloads

查看:176
本文介绍了使用HttpClient的异步文件下载时,线程问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这问题是一个后续使用HttpClient的异步文件下载的。

2015/01/15编辑在住宿增加对多线程 - 仍然有一个谜,

 使用系统;
使用System.Collections.Generic;
使用System.IO;
使用System.Net.Http;
使用System.Threading.Tasks;命名空间TestHttpClient2
{
  类节目
  {
    / *使用雅虎门户网站访问报价的股票 - 执行异步操作。 * /    静态字符串的baseUrl =htt​​p://real-chart.finance.yahoo.com/;
    静态字符串requestUrlFormat =/table.csv?s={0}&d=0&e=1&f=2016&g=d&a=0&b=1&c=1901&ignore=.csv ;    静态无效的主要(字串[] args)
    {
      VAR activeTaskList =新的List<任务>();      字符串输出目录=StockQuotes;
      如果(!Directory.Exists(输出目录))
      {
        Directory.CreateDirectory(输出目录);
      }      而(真)
      {
        Console.WriteLine(输入符号或回车键退出:);
        串符号=到Console.ReadLine();
        如果(string.IsNullOrEmpty(符号))
        {
          打破;
        }        任务downloadTask = DownloadDataForStockAsync(输出目录,符号);
        如果(TaskIsActive(downloadTask))
        {
          //这是一个异步的世界 - 更新前锁定列表!
          锁定(activeTaskList)
          {
            activeTaskList.Add(downloadTask);
          }        }
        其他
        {
          Console.WriteLine(任务已经完成???!?);
        }
        CleanupTasks(activeTaskList);
      }      Console.WriteLine(清理);
      而(CleanupTasks(activeTaskList))
      {
        Task.Delay(1).Wait();
      }
    }    私人静态布尔CleanupTasks(列表<任务> activeTaskList)
    {
      //反向环路允许列表项删除
      //这是一个异步的世界 - 更新前锁定列表!
      锁定(activeTaskList)
      {
        的for(int i = activeTaskList.Count - 1; I> = 0;我 - )
        {
          如果(!TaskIsActive(activeTaskList [I]))
          {
            activeTaskList.RemoveAt(ⅰ);
          }
        }
        返回activeTaskList.Count> 0;
      }
    }    私人静态布尔TaskIsActive(任务任务)
    {
      返回的任务!= NULL
          &功放;&安培; task.Status!= TaskStatus.Canceled
          &功放;&安培; task.Status!= TaskStatus.Faulted
          &功放;&安培; task.Status = TaskStatus.RanToCompletion!;
    }    静态异步任务DownloadDataForStockAsync(字符串输出目录,串符号)
    {
      尝试
      {
        使用(VAR的客户=新的HttpClient())
        {
          client.BaseAddress =新的URI(的baseUrl);
          client.Timeout = TimeSpan.FromMinutes(5);
          字符串requestUrl =的String.Format(requestUrlFormat,符号);          VAR要求=新的Htt prequestMessage(HttpMethod.Post,requestUrl);
          VAR sendTask = client.SendAsync(请求,HttpCompletionOption.ResponseHeadersRead);
          VAR响应=等待sendTask;
          response.EnsureSuccessStatus code();
          VAR httpStream =等待response.Content.ReadAsStreamAsync();          字符串timestampedName = FormatTimestampedString(符号,真正的);
          VAR文件路径= Path.Combine(输出目录,timestampedName +名为.csv);
          使用(VAR FILESTREAM = File.Create(文件路径))
          使用(VAR读者=新的StreamReader(httpStream))
          {
            等待httpStream.CopyToAsync(FILESTREAM);
            fileStream.Flush();
          }
        }
      }
      赶上(异常前)
      {
        Console.WriteLine(上线异常:{0}:{1} \\ r \\ n,
          System.Threading.Thread.CurrentThread.ManagedThreadId,
          ex.Message,
          ex.StackTrace);
      }
    }    静态挥发字符串lastTimestampedString =的String.Empty;
    静态挥发串假=的String.Empty;
    静态的HashSet<串GT; oldStrings =新的HashSet<串GT;();    静态字符串FormatTimestampedString(字符串消息,布尔uniquify = FALSE)
    {
      //这是一个异步的世界 - 在使用之前锁定共享资源!
      //锁(虚设)
      锁定(lastTimestampedString)
      {
        Console.WriteLine(IN - 主题:{0:D2} lastTimestampedString:{1},
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            lastTimestampedString);        串newTimestampedString;        而(真)
        {
          日期时间lastDateTime = DateTime.Now;          newTimestampedString =的String.format(
              {1:D4} _ {2} D2 _ {3} D2 _ {4} D2 _ {5} D2 _ {6} D2 _ {7:D3} _ {0},
                信息,
                lastDateTime.Year,lastDateTime.Month,lastDateTime.Day,
                lastDateTime.Hour,lastDateTime.Minute,lastDateTime.Second,
                lastDateTime.Millisecond
                );
          如果(!uniquify)
          {
            打破;
          }
          如果(newTimestampedString!= lastTimestampedString)
          {
            打破;
          }          //Task.Delay(1).Wait();
        };        lastTimestampedString = newTimestampedString;
        Console.WriteLine(OUT ​​- 主题:{0:D2} lastTimestampedString:{1},
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            lastTimestampedString);        如果(uniquify)
        {
          oldStrings.Add(lastTimestampedString);
        }
        返回lastTimestampedString;
      }
    }
  }
}

Q)为什么会出现这种间歇性的错误(在此输出的末端。)(我抄袭NES重复行到剪贴板,并粘贴到控制台的一个长长的清单,以重复的问题)

 输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
NES
输入符号或回车键退出:
IN - 主题:18 lastTimestampedString:
OUT ​​- 主题:18 lastTimestampedString:2015_01_15_11_19_44_472_NES
IN - 主题:17 lastTimestampedString:2015_01_15_11_19_44_472_NES
OUT ​​- 主题:17 lastTimestampedString:2015_01_15_11_19_44_473_NES
IN - 主题:19 lastTimestampedString:2015_01_15_11_19_44_473_NES
OUT ​​- 主题:19 lastTimestampedString:2015_01_15_11_19_44_493_NES
IN - 主题:16 lastTimestampedString:2015_01_15_11_19_44_493_NES
OUT ​​- 主题:16 lastTimestampedString:2015_01_15_11_19_44_494_NES
IN - 主题:18 lastTimestampedString:2015_01_15_11_19_44_494_NES
OUT ​​- 主题:18 lastTimestampedString:2015_01_15_11_19_44_495_NES
IN - 主题:17 lastTimestampedString:2015_01_15_11_19_44_495_NES
IN - 主题:16 lastTimestampedString:2015_01_15_11_19_44_495_NES
OUT ​​- 主题:17 lastTimestampedString:2015_01_15_11_19_44_496_NES
IN - 主题:19 lastTimestampedString:2015_01_15_11_19_44_495_NES
OUT ​​- 主题:19 lastTimestampedString:2015_01_15_11_19_44_496_NES
IN - 主题:18 lastTimestampedString:2015_01_15_11_19_44_496_NES
OUT ​​- 主题:16 lastTimestampedString:2015_01_15_11_19_44_495_NES
OUT ​​- 主题:18 lastTimestampedString:2015_01_15_11_19_44_497_NES
IN - 主题:19 lastTimestampedString:2015_01_15_11_19_44_497_NES
OUT ​​- 主题:19 lastTimestampedString:2015_01_15_11_19_44_523_NES
IN - 主题:18 lastTimestampedString:2015_01_15_11_19_44_523_NES
OUT ​​- 主题:18 lastTimestampedString:2015_01_15_11_19_44_532_NES
IN - 主题:19 lastTimestampedString:2015_01_15_11_19_44_532_NES
OUT ​​- 主题:19 lastTimestampedString:2015_01_15_11_19_44_533_NES
IN - 主题:18 lastTimestampedString:2015_01_15_11_19_44_533_NES
在线程异常:17:该进程无法访问该文件'C:\\用户\\ drogers \\ _ code \\测试\\ TestHttpClient \\ TestHttpClient2 \\ BIN \\调试\\ StockQuot
ES \\ 2015_01_15_11_19_44_495_NES.csv',因为它正由另一个进程使用。上线异常:16:该进程无法访问该文件'C:\\用户\\ drogers \\ _ code \\测试\\ TestHttpClient \\ TestHttpClient2 \\ BIN \\调试\\ StockQuot
ES \\ 2015_01_15_11_19_44_496_NES.csv',因为它正由另一个进程使用。OUT ​​- 主题:18 lastTimestampedString:2015_01_15_11_19_44_540_NES
IN - 主题:17 lastTimestampedString:2015_01_15_11_19_44_540_NES
IN - 主题:19 lastTimestampedString:2015_01_15_11_19_44_540_NES
OUT ​​- 主题:17 lastTimestampedString:2015_01_15_11_19_44_557_NES
OUT ​​- 主题:19 lastTimestampedString:2015_01_15_11_19_44_560_NES
在线程异常:19:该进程无法访问该文件'C:\\用户\\ drogers \\ _ code \\测试\\ TestHttpClient \\ TestHttpClient2 \\ BIN \\调试\\ StockQuot
ES \\ 2015_01_15_11_19_44_560_NES.csv',因为它正由另一个进程使用。

我能避免与线126的反注释的问题和评论127行,如:

  //这是一个异步的世界 - 在使用之前锁定共享资源!
  锁(虚设)
  //锁(lastTimestampedString)

综观IL,用于FormatTimestampedString产生在code中的唯一差别是

  ldsfld串modreq([mscorlib程序] System.Runtime.CompilerServices.IsVolatile)
            TestHttpClient2.Program :: ** ** lastTimestampedString

  ldsfld串modreq([mscorlib程序] System.Runtime.CompilerServices.IsVolatile)
            TestHttpClient2.Program :: ** **假人


解决方案

字符串是不可改变的。所以,当我把我锁在lastTimestampedString引用,然后改变了它,我不再有我以为我有锁。锁是在老的字符串。任何其他沿线未来将是对新的字符串测试锁,并会因此被允许的。

认错。

This question is a follow-up to Using HttpClient for Asynchronous File downloads.

2015/01/15 Edited to add in accomodation for multithreading - still have a mystery,

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace TestHttpClient2
{
  class Program
  {
    /* Use Yahoo portal to access quotes for stocks - perform asynchronous operations. */

    static string baseUrl = "http://real-chart.finance.yahoo.com/";
    static string requestUrlFormat = "/table.csv?s={0}&d=0&e=1&f=2016&g=d&a=0&b=1&c=1901&ignore=.csv";

    static void Main(string[] args)
    {
      var activeTaskList = new List<Task>();

      string outputDirectory = "StockQuotes";
      if (!Directory.Exists(outputDirectory))
      {
        Directory.CreateDirectory(outputDirectory);
      }

      while (true)
      {
        Console.WriteLine("Enter symbol or [ENTER] to exit:");
        string symbol = Console.ReadLine();
        if (string.IsNullOrEmpty(symbol))
        {
          break;
        }

        Task downloadTask = DownloadDataForStockAsync(outputDirectory, symbol);
        if (TaskIsActive(downloadTask))
        {
          // This is an asynchronous world - lock the list before updating it!
          lock (activeTaskList)
          {
            activeTaskList.Add(downloadTask);
          }

        }
        else
        {
          Console.WriteLine("task completed already?!??!?");
        }
        CleanupTasks(activeTaskList);
      }

      Console.WriteLine("Cleaning up");
      while (CleanupTasks(activeTaskList))
      {
        Task.Delay(1).Wait();
      }
    }

    private static bool CleanupTasks(List<Task> activeTaskList)
    {
      // reverse loop to allow list item deletions
      // This is an asynchronous world - lock the list before updating it!
      lock (activeTaskList)
      {
        for (int i = activeTaskList.Count - 1; i >= 0; i--)
        {
          if (!TaskIsActive(activeTaskList[i]))
          {
            activeTaskList.RemoveAt(i);
          }
        }
        return activeTaskList.Count > 0;
      }
    }

    private static bool TaskIsActive(Task task)
    {
      return task != null
          && task.Status != TaskStatus.Canceled
          && task.Status != TaskStatus.Faulted
          && task.Status != TaskStatus.RanToCompletion;
    }

    static async Task DownloadDataForStockAsync(string outputDirectory, string symbol)
    {
      try
      {
        using (var client = new HttpClient())
        {
          client.BaseAddress = new Uri(baseUrl);
          client.Timeout = TimeSpan.FromMinutes(5);
          string requestUrl = string.Format(requestUrlFormat, symbol);

          var request = new HttpRequestMessage(HttpMethod.Post, requestUrl);
          var sendTask = client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
          var response = await sendTask;
          response.EnsureSuccessStatusCode();
          var httpStream = await response.Content.ReadAsStreamAsync();

          string timestampedName = FormatTimestampedString(symbol, true);
          var filePath = Path.Combine(outputDirectory, timestampedName + ".csv");
          using (var fileStream = File.Create(filePath))
          using (var reader = new StreamReader(httpStream))
          {
            await httpStream.CopyToAsync(fileStream);
            fileStream.Flush();
          }
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine("Exception on thread: {0}: {1}\r\n",
          System.Threading.Thread.CurrentThread.ManagedThreadId,
          ex.Message,
          ex.StackTrace);
      }
    }

    static volatile string lastTimestampedString = string.Empty;
    static volatile string dummy = string.Empty;
    static HashSet<string> oldStrings = new HashSet<string>();

    static string FormatTimestampedString(string message, bool uniquify = false)
    {
      // This is an asynchronous world - lock the shared resource before using it!
      //lock (dummy)
      lock (lastTimestampedString)
      {
        Console.WriteLine("IN  - Thread: {0:D2} lastTimestampedString: {1}", 
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            lastTimestampedString);

        string newTimestampedString;

        while (true)
        {
          DateTime lastDateTime = DateTime.Now;

          newTimestampedString = string.Format(
              "{1:D4}_{2:D2}_{3:D2}_{4:D2}_{5:D2}_{6:D2}_{7:D3}_{0}",
                message,
                lastDateTime.Year, lastDateTime.Month, lastDateTime.Day,
                lastDateTime.Hour, lastDateTime.Minute, lastDateTime.Second,
                lastDateTime.Millisecond
                );
          if (!uniquify)
          {
            break;
          }
          if (newTimestampedString != lastTimestampedString)
          {
            break;
          }

          //Task.Delay(1).Wait();
        };

        lastTimestampedString = newTimestampedString;
        Console.WriteLine("OUT - Thread: {0:D2} lastTimestampedString: {1}",
            System.Threading.Thread.CurrentThread.ManagedThreadId,
            lastTimestampedString);

        if (uniquify)
        {
          oldStrings.Add(lastTimestampedString);
        }
        return lastTimestampedString;
      }
    }
  }
}

Q) Why am I getting this intermittent error (at the end of this output.) (I am copying a long list of repeated lines of "NES" to the clipboard and pasting into the console in order to duplicate the problem):

Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
NES
Enter symbol or [ENTER] to exit:
IN  - Thread: 18 lastTimestampedString:
OUT - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_472_NES
IN  - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_472_NES
OUT - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_473_NES
IN  - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_473_NES
OUT - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_493_NES
IN  - Thread: 16 lastTimestampedString: 2015_01_15_11_19_44_493_NES
OUT - Thread: 16 lastTimestampedString: 2015_01_15_11_19_44_494_NES
IN  - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_494_NES
OUT - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_495_NES
IN  - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_495_NES
IN  - Thread: 16 lastTimestampedString: 2015_01_15_11_19_44_495_NES
OUT - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_496_NES
IN  - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_495_NES
OUT - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_496_NES
IN  - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_496_NES
OUT - Thread: 16 lastTimestampedString: 2015_01_15_11_19_44_495_NES
OUT - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_497_NES
IN  - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_497_NES
OUT - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_523_NES
IN  - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_523_NES
OUT - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_532_NES
IN  - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_532_NES
OUT - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_533_NES
IN  - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_533_NES
Exception on thread: 17: The process cannot access the file 'C:\Users\drogers\_code\Tests\TestHttpClient\TestHttpClient2\bin\Debug\StockQuot
es\2015_01_15_11_19_44_495_NES.csv' because it is being used by another process.

Exception on thread: 16: The process cannot access the file 'C:\Users\drogers\_code\Tests\TestHttpClient\TestHttpClient2\bin\Debug\StockQuot
es\2015_01_15_11_19_44_496_NES.csv' because it is being used by another process.

OUT - Thread: 18 lastTimestampedString: 2015_01_15_11_19_44_540_NES
IN  - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_540_NES
IN  - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_540_NES
OUT - Thread: 17 lastTimestampedString: 2015_01_15_11_19_44_557_NES
OUT - Thread: 19 lastTimestampedString: 2015_01_15_11_19_44_560_NES
Exception on thread: 19: The process cannot access the file 'C:\Users\drogers\_code\Tests\TestHttpClient\TestHttpClient2\bin\Debug\StockQuot
es\2015_01_15_11_19_44_560_NES.csv' because it is being used by another process.

I can avoid the problem with the uncommenting of line 126 and commenting line 127, as in:

  // This is an asynchronous world - lock the shared resource before using it!
  lock (dummy)
  //lock (lastTimestampedString)

Looking at the il, the only difference in the code generated for FormatTimestampedString is

ldsfld     string modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)
            TestHttpClient2.Program::**lastTimestampedString**

versus

ldsfld     string modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) 
            TestHttpClient2.Program::**dummy**

解决方案

Strings are immutable. So when I set my lock on the lastTimestampedString reference, and then changed it, I no longer had the lock I thought I had. The lock was on the old string. Anyone else coming along would be testing the lock on the new string, and would therefore be allowed in.

Mea culpa.

这篇关于使用HttpClient的异步文件下载时,线程问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆