2016-03-05 40 views
0

本來我寫了使用線程和ThreadPooling我的C#程序,但大多數在這裏的用戶都告訴我異步是爲了提高效率的更好方法。我的程序將JSON對象發送到服務器,直到返回狀態代碼200,然後轉到下一個任務。如何讓任務更獨立?

的問題是,一旦我的任務之一取回的200狀態碼,它等待其它任務,以獲得200碼,然後移動到下一個任務。我希望每個任務都能夠繼續執行下一個任務,而無需等待其他任務通過接收200響應來完成(或趕上)任務。

下面是我的並行運行我的任務主類。

public static void Main (string[] args) 
    { 
     var tasks = new List<Task>(); 
     for (int i = 0; i < 10; i++) { 
      tasks.Add (getItemAsync (i)); 
     } 

     Task.WhenAny (tasks); 
    } 

這是實際發送信息到另一臺服務器的getItemAsync()方法。在此方法起作用之前,它需要一個密鑰。問題也出在這裏,假設我運行了100個任務,所有100個任務都會等到每個人都擁有一個密鑰。

public static async Task getItemAsync (int i) 
    { 
     if (key == "") { 
      await getProductKey (i).ConfigureAwait(false); 
     } 

     Uri url = new Uri ("http://www.website.com/"); 

     var content = new FormUrlEncodedContent (new[] { 
      ... 
     }); 

     while (!success) { 
      using (HttpResponseMessage result = await client.PostAsync (url, content)) { 
       if (result.IsSuccessStatusCode) { 
        string resultContent = await result.Content.ReadAsStringAsync().ConfigureAwait(false); 

        Console.WriteLine(resultContent); 

        success=true; 
       } 
      } 
     } 

     await doMoreAsync (i); 

    } 

下面是檢索鍵的功能,它使用的HttpClient和被解析。

public static async Task getProductKey (int i) 
    { 
     string url = "http://www.website.com/key"; 

     var stream = await client.GetStringAsync (url).ConfigureAwait(false); 

     var doc = new HtmlDocument(); 
     doc.LoadHtml (stream); 

     HtmlNode node = doc.DocumentNode.SelectNodes ("//input[@name='key']") [0]; 

     try { 
      key = node.Attributes ["value"].Value; 
     } catch (Exception e) { 
      Console.WriteLine ("Exception: " + e); 
     } 
    } 

在每個任務收到一個密鑰並具有200狀態碼後,它將運行doMoreAsync()。我希望任何檢索200代碼的單個Task都能夠運行doMoreAsync(),而無需等待其他任務趕上。我如何去做這件事?

回答

0

你的主要方法是不是等待任務來完成,它甫一火災一串的異步任務並返回。 如果你想等待一個異步任務,你只能做的是從異步方法。解決方法是從Main方法揭開序幕一個異步任務,並使用攔截Task.Wait()等待其完成:

public static void Main(string[] args) { 
    Task.Run(async() => 
    { 
     var tasks = new List<Task>(); 
     for (int i = 0; i < 10; i++) { 
      tasks.Add (getItemAsync (i)); 
     } 

     var finishedTask = await Task.WhenAny(tasks); // This awaits one task 
    }).Wait(); 
} 

當你從一個異步調用的方法getItemAsync(),你可以刪除ConfigureAwait(false)爲好。 ConfigureAwait(false)只是確保一些代碼不會在UI線程上執行。

如果你想另一個任務追加到任務,也可以直接使用ContinueWith()追加到以前的任務:

getItemAsync(i).ContinueWith(anotherTask); 
+0

感謝您的回覆,我幾分鐘前發現了ContinueWith,現在我正在測試該方法。它不斷返回相同的線程#。和var finishedTask =等待tasks.WhenAny()不起作用。它說「System.Collections.Generic.list」不包含「whenany」的定義。 –

+0

我的意思是'等待Task.WhenAny(任務);'這將等待列表中的一個任務完成。您可以在完成後從列表中刪除該任務。 另外任務不會映射到線程1:1。一堆任務可以在同一個線程內執行。任務可以在另一個人等待某件事情時執行。 –

+0

是的,它不在我的主要工作。它扔我system.collections.generic.list錯誤。 –

0

你的主要問題似乎是,你共享key場跨所有同時運行的異步操作。這將不可避免地導致種族危害。相反,你應該改變你的getProductKey方法每個檢索鍵作爲它的異步操作的結果:

     // ↓ result type of asynchronous operation 
public static async Task<string> getProductKey(int i) 
{ 
    // retrieval logic here 

    // return key as result of asynchronous operation 
    return node.Attributes["value"].Value; 
} 

然後,你使用它,像這樣:

public static async Task getItemAsync(int i) 
{ 
    string key; 
    try 
    { 
     key = await getProductKey(i).ConfigureAwait(false); 
    } 
    catch 
    { 
     // handle exceptions 
    } 

    // use local variable 'key' in further asynchronous operations 
} 

最後,在你的主邏輯,使用WaitAll可防止控制檯應用程序在完成所有任務之前終止。雖然WaitAll是一個阻塞調用,因爲你希望主線程阻塞正是在這樣的情況下,可以接受的。

public static void Main (string[] args) 
{ 
    var tasks = new List<Task>(); 
    for (int i = 0; i < 10; i++) { 
     tasks.Add(getItemAsync(i)); 
    } 

    Task.WaitAll(tasks.ToArray()); 
} 
+0

是的,我嘗試了WaitAll()方法,但它不斷給我這個奇怪的錯誤,說:「參數#1無法轉換'system.collections.generic.list '表達式鍵入'系統。 threading.task.tasks []「 –

+0

@CO:我在'tasks'列表中缺少'.ToArray()'調用。嘗試更新的版本。 – Douglas

+0

是的,我早就明白這一點了。它仍然不能獨立工作,但程序不會很快關閉。它仍在等待所有的任務檢索那裏的密鑰,以繼續下一步。 –