繁体   English   中英

从C#中的多个异步方法(即线程)增加值类型

[英]Increase a value type from multiple async methods (i.e. threads) in C#

我需要从C#中的多个线程(异步方法)中增加一个计数器。

  • 我无法传递ref参数,因为它是一个异步方法
  • 我不能只是更新值(或锁定它),因为它是一个值类型
  • 我无法使用互锁,因为我无法在异步方法中将引用添加到计数器

所以我唯一想做的就是做一个像List<int>这样的愚蠢的东西,并将我的int放在那里,以便线程可以锁定List并更新值。

我希望这是一个已知的用例,并且有更优雅的方法吗?

这是一个小示例,不用担心语法问题:

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;}}class IntHolder { public int Value {get;set;}}可以通过引用传递并锁定(或使用Interlocked on)

非常感谢!

您可以在任何对象上使用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类型变量的名称。

如果您希望能够跨异步方法传递值,则可以利用AsyncLocal<T>

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

以为我会发布完整的代码示例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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM