【问题标题】:Increase a value type from multiple async methods (i.e. threads) in C#从 C# 中的多个异步方法(即线程)增加值类型
【发布时间】:2017-04-28 23:06:25
【问题描述】:

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

  • 我无法传递 ref 参数,因为它是异步方法
  • 我不能只更新值(或锁定它),因为它是一种值类型
  • 我无法使用 Interlocked,因为我无法在异步方法中将 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;}},可以通过引用传递并锁定(或使用Interlocked on)

非常感谢!

【问题讨论】:

  • 您能否向我们展示一个简单而完整的问题重现? This 工作正常。
  • 为此目的使用包装类有什么问题?只需使用唯一一个 int 字段创建一个类。
  • 我似乎记得 .net 框架具有专门为您所描述的内容而设计的特定类。也许在任务并行库中?或者,您可以锁定 Object 的一个实例。正如@AndreyNasonov 评论的那样,您可以在包装类中实现它。
  • 谢谢大家。 @YuvalItzchakov 我添加了一些代码示例来说明问题。

标签: c# multithreading asynchronous async-await


【解决方案1】:

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

例如:

object locking_object = new object();

这将创建一个仅用于锁定的对象。

然后,当你想增加值时:

lock(locking_object)
{
    integer++;
}

根据 cmets 更新:

创建一个类来保存整数值,如下所示:

class IntHolder
{
    public int Value;
}

您可以使用Interlocked 类执行以下操作:

Interlocked.Increment(ref int_holder.Value);

其中int_holder 是您传递给方法的IntHolder 类型变量的名称。

【讨论】:

  • 整数是值类型。如果我不能使用ref,我如何将它放入以修改原始值的方式增加的异步方法中?
  • @Miquel 您显然需要能够从所有线程访问int。要么让它成为所有线程都可以访问的类的成员,要么用IntHolder 类包装它,并将对类实例的引用传递给所有线程。
  • 实际上,当你这样做时,使用Interlocked类而不是使用lock在性能方面会更好
  • IntHolder 是你自己的班级。 class IntHolder { public int Value {get;set;}} 这样做的目的只是为了包装值类型,因此您可以将整个包作为引用类型传递。你可以随便叫它。
  • 我猜 Yorye Nathan 正在谈论一个自定义类。会是这样的class IntHolder { public int Value;}
【解决方案2】:

如果您希望能够在异步方法之间传递值,也许您可​​以利用AsyncLocal&lt;T&gt;

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

【讨论】:

  • 这很酷。就像为此目的的内置包装器一样。但是这和使用你自己的类来包裹int有什么区别吗?
  • 是的,AsyncLocal&lt;T&gt; 很酷,但不符合作者的需求。作者希望跨多个异步方法/线程使用全局变量,而不是局部值存储。
  • @YoryeNathan 如果您至少使用this constructor,请更改通知。谢谢!
  • @Andrey 如果该字段是类级别的变量,它应该在异步方法调用中可用并在它们之间流动。它不会为每次调用分配一个新值。
  • 我试过你的sn-p。它给了我错误:“属性或索引器不能作为 out 或 ref 参数传递”
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    • 1970-01-01
    • 2021-07-10
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多