2015-09-28 27 views
3

我需要增加C#中多個線程(異步方法)的計數器。從C#中的多個異步方法(即線程)增加值類型

  • 我無法通過ref參數,因爲它是一個異步方法
  • 我不能只更新值(或在其鎖定),因爲它是一個值類型
  • 因爲我不能把我不能使用互鎖一個裁判在異步方法櫃檯

所以我想出的唯一的事情就是讓一些愚蠢像一個List<int>,並把我的int中有那麼線程可以在列表鎖定並更新值。

我希望這是一個已知的用例,並有一個更優雅的方式來做到這一點?

這裏有一個小例子,更不用說小的語法問題:

public void DoStuff() 
{ 
    int counter; 
    var tasks = new List<Task>() 
    for(int i = 0; i < 10; i++) 
    { 
     tasks.Add(AsyncMethod(<SOMETHING>)); 
    } 
    Task.WaitAll(tasks); 
    Console.WriteLine("Total: {0}", counter); 
} 

public async Task AsyncMethod(<SOMETHING>) 
{ 
    // Lock if needed by <SOMETHING> 
    <SOMETHING>+=20; 
} 

我需要創建一個類與int領域,還是C#提供一些現成的盒子?我並沒有停留在這一點上,只是試圖從後見之門學習如果有更好的方法。謝謝!

爲將來用戶: 的共識似乎是創建如class IntHolder { public int Value {get;set;}}可以通過引用傳遞並鎖定在一個自定義類(或使用互鎖)

非常感謝大家!

+1

能否請您告訴我們您的問題的一個簡單而完整的再現? [這](http://tryroslyn.azurewebsites.net/#f:r/K4Zwlgdg5gBAygTxAFwKYFsDcAoUlaIoYB0AKgBYBOqAhgCb5k0gDWIOe08Sa6ZVtBtBzYADsABGAGzABjGLKnMQMAMIwA3thg6Y46XJjMEEeaWYsYAWQAUASk3bdzyMhhgYAXhgAGHM+caAHcaMDdzVmIATTBUKTp7fwCdAEkINEopAHtZFlQ6YjTZanRUdJtqADN3OySdAF9seqAA=)工作正常。 –

+0

爲此目的使用包裝類有什麼問題?只用一個'int'字段創建一個類。 –

+0

我似乎記得.net框架有專門爲您正在描述的內容而設計的類。也許在任務並行庫中?或者,您可以鎖定Object的實例。正如@AndreyNasonov所評論的,你可以在包裝類中實現它。 – Bovaz

回答

4

您可以在任何對象上使用lock,而不僅僅是要使用的對象。

例如:

object locking_object = new object(); 

這產生一個僅用於鎖定被使用的對象。

再後來,當你想增加值:

lock(locking_object) 
{ 
    integer++; 
} 

更新基礎上的評論:

創建一個類來保存整數值是這樣的:

class IntHolder 
{ 
    public int Value; 
} 

您可以使用Interlocked類來做這樣的事情:

Interlocked.Increment(ref int_holder.Value); 

其中int_holder是您傳遞給您的方法的變量類型IntHolder的名稱。

+0

該整數是一個值類型。如果我不能使用'ref',我將如何將它加入到異步方法中,以增加修改原始值的方式? – Miquel

+2

@Miquel你顯然需要能夠從所有線程訪問'int'。通過使其成爲所有人都可訪問的類的成員,或者使用IntHolder類包裝它,並將對類實例的引用傳遞給所有線程。 – SimpleVar

+0

其實,當你這樣做,這將是在性能使用'Interlocked'類而不是使用方面更好'lock' –

1

如果你想成爲能夠流動橫跨異步方法的價值,或許你可以採取的AsyncLocal<T>優勢:

private AsyncLocal<int> counter = new AsyncLocal<int>(); 
public async Task FooAsync() 
{ 
    await Task.Yield(); 
    Interlocked.Increment(ref counter.Value); 
} 
+0

很酷。就像一個完全用於此目的的內置包裝。但是,這和使用你自己的類來包裝'int'有什麼區別嗎? – SimpleVar

+0

是的,'AsyncLocal '是很酷,但它不符合作者的需求。作者希望跨多個異步方法/線程使用全局變量,但不是本地值存儲。如果你使用 –

+1

@YoryeNathan變更通知[此構造]至少是(https://msdn.microsoft.com/en-us/library/dn906228(V = vs.110)的.aspx)。謝謝! – Miquel

0

想我會發佈一個完整的代碼示例B/C一些事情可能會非常棘手。

public class MyIntIncrementer 
    { 
     public int MyInt = 0; 
    } 

    public static String TimeStamp 
    { 
     get { return DateTime.UtcNow.ToString("HH:mm:ss.fff"); } //yyyy-MM-dd 
    } 

    public static void Main(string[] args) 
    { 
     List<Task<string>> tasks = new List<Task<string>>(); 
     int waitSeconds = 5; 

     Console.WriteLine(String.Format("{0}: Start", TimeStamp)); 
     DateTime start = DateTime.Now; 

     MyIntIncrementer iIncrementer = new MyIntIncrementer(); 
     iIncrementer.MyInt = 0; 

     for (int i = 0; i < 10; i++) 
     { 
      //definitely loops and changes values - but when passed in to the function they don't remain that way... see iParam 
      //Console.WriteLine(String.Format("{0}: Looping... i: {1}\n", TimeStamp,i)); 

      tasks.Add(Task.Run(() => 
      { 
       // all have 10 => last value :(
       // Console.WriteLine(String.Format("{0}: Running... i: {1}\n", TimeStamp, i)); 

       return SayYesIfEven(waitSeconds, i, iIncrementer); 
      })); 
     } 

     Console.WriteLine(String.Format("{0}: Before Wait...", TimeStamp)); 

     // wait for them to run 
     Task.WhenAll(tasks).Wait(); 
     //Task.WhenAll(tasks); // doesn't wait with .Wait() 

     Console.WriteLine(String.Format("{0}: After Wait... Results:", TimeStamp)); 

     // get the results 
     for (int i = 0; i < tasks.Count; i++) 
     { 
      Console.WriteLine(tasks[i].Result); 
     } 

     Console.WriteLine(String.Format("{0}: Done ({1}s)", TimeStamp, (DateTime.Now - start).TotalSeconds)); 
    } 

    public static async Task<string> SayYesIfEven(int waitSeconds, int iParam, MyIntIncrementer iIncrementer) 
    { 
     int localIParamStart = (int)iParam; // no difference from passed in value when copied locally 

     int currentIStart = iIncrementer.MyInt; // not guaranteed to be unique 

     // iParam is the last value and when 'iIncrementer.MyInt' prints here, it's sometimes the same in multiple threads 
     Console.WriteLine(String.Format("{0:00}: Before Increment: even? {1} <=> {2:00}/iP: {3:00}/LiP: {4:00}/in.mP: {5:00}", TimeStamp, (currentIStart % 2 == 0 ? "Yes" : "No "), currentIStart, iParam, localIParamStart, iIncrementer.MyInt)); 

     // best way to get a unique value 
     int currentIR = Interlocked.Increment(ref iIncrementer.MyInt); // all threads wait on a lock to increment and then they move forward with their own values 
     int currentI = iIncrementer.MyInt; 
     int localIParam = (int)iParam; 

     Console.WriteLine(String.Format("{0:00}: After Increment: even? {1} <=> {2:00} => {6:00} => {7:00}/iP: {3:00}/LiP: {4:00} => {8:00}/in.mP: {5:00}", TimeStamp, (currentI % 2 == 0 ? "Yes" : "No "), currentIStart, iParam, localIParamStart, iIncrementer.MyInt, currentIR, currentI, localIParam)); 

     await Task.Delay(waitSeconds * 1000); // simulate delay 

     await Task.Run(() => 
     { 
      // do other stuff... 

      // iParam and iIncrementer.value have the last value (note that this statement runs after the above delay) 
      Console.WriteLine(String.Format("{0:00}: Inside Run after Delay: even? {1} <=> {2:00} => {6:00} => {7:00}/iP: {3:00}/LiP: {4:00} => {8:00}/in.mP: {5:00}", TimeStamp, (currentI % 2 == 0 ? "Yes" : "No "), currentIStart, iParam, localIParamStart, iIncrementer.MyInt, currentIR, currentI, localIParam)); 
      return "something"; 
     }); 

     // all have last value when showing what was passed into SayYesIfEven - and iIncrementer.value is also the last value 
     return (String.Format("{0:00}: Returning: even? {1} <=> {2:00} => {6:00} => {7:00}/iP: {3:00}/LiP: {4:00} => {8:00}/in.mP: {5:00}", TimeStamp, (currentI % 2 == 0 ? "Yes" : "No "), currentIStart, iParam, localIParamStart, iIncrementer.MyInt, currentIR, currentI, localIParam)); 
    } 

輸出:

13:55:35.340: Start 
    13:55:35.357: Before Wait... 

    // rearranged to show before/after values side by side 
    // note the duplicate values for MyIntIncrementer.MyInt - and last values for iParam 

    13:55:35.357: Before Increment: even? Yes <=> 00/iP: 10/LiP: 10/in.mP: 00 
    13:55:35.357: Before Increment: even? Yes <=> 00/iP: 10/LiP: 10/in.mP: 00 
    13:55:35.371: Before Increment: even? Yes <=> 02/iP: 10/LiP: 10/in.mP: 02 
    13:55:35.371: Before Increment: even? No <=> 03/iP: 10/LiP: 10/in.mP: 03 
    13:55:35.371: Before Increment: even? Yes <=> 04/iP: 10/LiP: 10/in.mP: 04 
    13:55:35.371: Before Increment: even? No <=> 05/iP: 10/LiP: 10/in.mP: 05 
    13:55:35.371: Before Increment: even? Yes <=> 06/iP: 10/LiP: 10/in.mP: 06 
    13:55:35.371: Before Increment: even? No <=> 07/iP: 10/LiP: 10/in.mP: 07 
    13:55:35.371: Before Increment: even? No <=> 07/iP: 10/LiP: 10/in.mP: 07 
    13:55:35.371: Before Increment: even? Yes <=> 00/iP: 10/LiP: 10/in.mP: 00 

    // after the locked increment, notice we have reliable independent values 

    13:55:35.371: After Increment: even? Yes <=> 00 => 02 => 02/iP: 10/LiP: 10 => 10/in.mP: 02 
    13:55:35.371: After Increment: even? No <=> 00 => 01 => 01/iP: 10/LiP: 10 => 10/in.mP: 01 
    13:55:35.371: After Increment: even? No <=> 02 => 03 => 03/iP: 10/LiP: 10 => 10/in.mP: 03 
    13:55:35.371: After Increment: even? Yes <=> 03 => 04 => 04/iP: 10/LiP: 10 => 10/in.mP: 04 
    13:55:35.371: After Increment: even? No <=> 04 => 05 => 05/iP: 10/LiP: 10 => 10/in.mP: 05 
    13:55:35.371: After Increment: even? Yes <=> 05 => 06 => 06/iP: 10/LiP: 10 => 10/in.mP: 06 
    13:55:35.371: After Increment: even? No <=> 06 => 07 => 07/iP: 10/LiP: 10 => 10/in.mP: 07 
    13:55:35.371: After Increment: even? Yes <=> 07 => 08 => 08/iP: 10/LiP: 10 => 10/in.mP: 08 
    13:55:35.371: After Increment: even? No <=> 07 => 09 => 09/iP: 10/LiP: 10 => 10/in.mP: 09 
    13:55:35.371: After Increment: even? Yes <=> 00 => 10 => 10/iP: 10/LiP: 10 => 10/in.mP: 10 

    13:55:40.381: Inside Run after Delay: even? Yes <=> 07 => 08 => 08/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? Yes <=> 05 => 06 => 06/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? No <=> 07 => 09 => 09/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? No <=> 04 => 05 => 05/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? No <=> 02 => 03 => 03/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? No <=> 00 => 01 => 01/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? Yes <=> 00 => 10 => 10/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? Yes <=> 00 => 02 => 02/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? Yes <=> 03 => 04 => 04/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Inside Run after Delay: even? No <=> 06 => 07 => 07/iP: 10/LiP: 10 => 10/in.mP: 10 

    // notice at the bottom of the call - MyIntIncrementer.MyInt is the last value and thus never unique 
    // - only the initial value (obtained after the lock and before any delay) is still reliable - same behavior found on a 100+ loop 

    13:55:40.381: After Wait... Results: 

    13:55:40.381: Returning: even? Yes <=> 00 => 10 => 10/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? No <=> 00 => 01 => 01/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? Yes <=> 00 => 02 => 02/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? No <=> 02 => 03 => 03/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? Yes <=> 03 => 04 => 04/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? No <=> 04 => 05 => 05/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? Yes <=> 05 => 06 => 06/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? No <=> 06 => 07 => 07/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? Yes <=> 07 => 08 => 08/iP: 10/LiP: 10 => 10/in.mP: 10 
    13:55:40.381: Returning: even? No <=> 07 => 09 => 09/iP: 10/LiP: 10 => 10/in.mP: 10 

    13:55:40.381: Done (5.0410934s)