[英]Increase a value type from multiple async methods (i.e. threads) in C#
我需要从C#中的多个线程(异步方法)中增加一个计数器。
所以我唯一想做的就是做一个像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.