2015-01-13 3 views
0

이 질문은 후속 조치로 Using HttpClient for Asynchronous File downloads입니다.비동기 파일 다운로드에 HttpClient를 사용할 때 스레딩 문제가 발생합니다.

2015년 1월 15일 편집은 멀티 스레딩을위한 숙박에 추가 - 여전히 신비가,

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)를하는 이유는이 출력의 끝이 간헐적으로 오류를 (얻고) (나는. 위해 콘솔에 클립 보드 및 붙여 넣기를 "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: 
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. 

내가 라인 (126)의 주석을 해제와 문제 및 라인 (127) 주석을 피할 수 있습니다, 예 :

01 당신은 같은 파일에 주식 데이터를 작성하려고이 작업을

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

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

답변

0

문자열을 변경할 수 없습니다. 따라서 lastTimestampedString 참조에 대한 자물쇠를 설정 한 다음 변경하면 더 이상 자물쇠가 있다고 생각하지 않습니다. 잠금 장치는 이전 문자열에있었습니다. 따라 오는 다른 사람은 새 문자열에 대한 잠금을 테스트하므로 허용됩니다.

Mea culpa.

1

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

는 위원장을 보면, FormatTimestampedString에 대해 생성 된 코드에서 유일한 차이점이다.

FormatTimestampedString을 변경하여 생성 파일 이름의 존재 여부를 확인하고 존재하는 경우 새 파일을 생성하십시오.

+1

예외가 발생해도 파일을 쓰지 마십시오. 동일한 정확한 타임 스탬프를 사용하여 밀리 초 단위로 데이터를 작성하는 것은 실제로는 유용하지 않을 것입니다. 동일한 이름을 가진 파일이 이미 있으면 파일을 다시 쓰는 것을 건너 뛸 수 있습니다. 경고 : 당신은 이것을 테스트하기 위해'File.Exists()'를 호출 할 수 없다; 실제로 파일을 만들려고 시도하고 예외가 발생하면 catch해야합니다. 'FileStream'을'FileMode.CreateNew'와 함께 직접 사용하는 것을 고려해보고 가능한 예외와 이미 존재하는 파일을 더 쉽게 구분할 수 있도록하십시오. –

+0

요점은 선택한 명명 알고리즘을 기반으로 중복 된 리소스 이름을 만드는 것을 피하는 것입니다. –

관련 문제