Как обеспечить потокобезопасность сервисов в DI контейнере (в частности Singleton)
Написал небольшое приложение с использованием DI для консоли. Приложение измменяет и выводит состояние синглтона из DI контейнера.
Program.cs
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ISingletonTest, SingletonTest>();
serviceCollection.AddSingleton<App>();
var serviceProvider = serviceCollection.BuildServiceProvider();
var app = serviceProvider?.GetService<App>();
using (var scope = serviceProvider?.CreateScope())
{
app?.RUN();
}
I/SingletonTest.cs
public interface ISingletonTest
{
public int COUNT { get; }
public void increase();
}
class SingletonTest : ISingletonTest
{
private int _count = 0;
public int COUNT { get => _count; }
public void increase()
{
_count++;
}
}
App.cs
internal class App
{
private readonly ISingletonTest _singletonTest;
public App(ISingletonTest singletonTest)
{
_singletonTest = singletonTest;
}
public void RUN()
{
List<Thread> lst = new List<Thread>();
for (int i = 0; i < 100; i++)
{
lst.Add(new Thread(() =>
{
_singletonTest.increase();
Console.WriteLine("In thread:" + Environment.CurrentManagedThreadId + " value of _singletonTest.COUNT = " + _singletonTest.COUNT);
}));
}
foreach (var item in lst)
{
item.Start();
}
Console.ReadLine();
}
}
И вывод программы вот такой
Хочу разобраться почему так происходит, ведь ServiceCollection использует потокобезопасную коллекцию на основе IList. И по сути дела данные могут выводиться не по порядку, но их дублирования точно не должно быть. Да! Вывод иногда нормальный, но и бывает повторение чтения по несколько раз. Помогите разобраться с вопросом потокобезопасности в DI контейнере.
Ответы (1 шт):
Проблема здесь не в DI, а в том что сам класс реализован не потокобезопасно.
Вот например так можно.
public interface ISingletonTest
{
public int Increase();
}
class SingletonTest : ISingletonTest
{
private int _count = 0;
public int Increase()
{
return Interlocked.Increment(ref _count);
}
}
И вот так тестировать. Немного упростил код, но логика не поменялась.
internal class App
{
private readonly ISingletonTest _singletonTest;
public App(ISingletonTest singletonTest)
{
_singletonTest = singletonTest;
}
public void RUN()
{
for (int i = 0; i < 100; i++)
{
new Thread(() =>
{
int count = _singletonTest.Increase();
Console.WriteLine("In thread:" + Environment.CurrentManagedThreadId + " value of _singletonTest.COUNT = " + count);
}).Start();
}
Console.ReadLine();
}
}
Вывод в консоль такой
In thread:15 value of _singletonTest.COUNT = 3
In thread:13 value of _singletonTest.COUNT = 1
In thread:14 value of _singletonTest.COUNT = 2
In thread:16 value of _singletonTest.COUNT = 4
In thread:17 value of _singletonTest.COUNT = 5
In thread:18 value of _singletonTest.COUNT = 6
In thread:19 value of _singletonTest.COUNT = 7
In thread:20 value of _singletonTest.COUNT = 8
In thread:21 value of _singletonTest.COUNT = 9
In thread:22 value of _singletonTest.COUNT = 10
In thread:23 value of _singletonTest.COUNT = 11
In thread:24 value of _singletonTest.COUNT = 12
In thread:25 value of _singletonTest.COUNT = 13
In thread:26 value of _singletonTest.COUNT = 14
In thread:27 value of _singletonTest.COUNT = 15
In thread:28 value of _singletonTest.COUNT = 16
In thread:29 value of _singletonTest.COUNT = 17
In thread:30 value of _singletonTest.COUNT = 18
In thread:31 value of _singletonTest.COUNT = 19
In thread:32 value of _singletonTest.COUNT = 20
In thread:33 value of _singletonTest.COUNT = 21
In thread:34 value of _singletonTest.COUNT = 22
In thread:35 value of _singletonTest.COUNT = 23
In thread:36 value of _singletonTest.COUNT = 24
In thread:37 value of _singletonTest.COUNT = 25
In thread:38 value of _singletonTest.COUNT = 26
In thread:39 value of _singletonTest.COUNT = 27
In thread:40 value of _singletonTest.COUNT = 28
In thread:41 value of _singletonTest.COUNT = 29
In thread:42 value of _singletonTest.COUNT = 30
In thread:43 value of _singletonTest.COUNT = 31
In thread:44 value of _singletonTest.COUNT = 32
In thread:45 value of _singletonTest.COUNT = 33
In thread:46 value of _singletonTest.COUNT = 34
In thread:47 value of _singletonTest.COUNT = 35
In thread:48 value of _singletonTest.COUNT = 36
In thread:49 value of _singletonTest.COUNT = 37
In thread:50 value of _singletonTest.COUNT = 38
In thread:51 value of _singletonTest.COUNT = 39
In thread:52 value of _singletonTest.COUNT = 40
In thread:53 value of _singletonTest.COUNT = 41
In thread:54 value of _singletonTest.COUNT = 42
In thread:55 value of _singletonTest.COUNT = 43
In thread:56 value of _singletonTest.COUNT = 44
In thread:57 value of _singletonTest.COUNT = 45
In thread:58 value of _singletonTest.COUNT = 46
In thread:59 value of _singletonTest.COUNT = 47
In thread:60 value of _singletonTest.COUNT = 48
In thread:61 value of _singletonTest.COUNT = 49
In thread:62 value of _singletonTest.COUNT = 50
In thread:63 value of _singletonTest.COUNT = 51
In thread:64 value of _singletonTest.COUNT = 52
In thread:65 value of _singletonTest.COUNT = 53
In thread:66 value of _singletonTest.COUNT = 54
In thread:67 value of _singletonTest.COUNT = 55
In thread:68 value of _singletonTest.COUNT = 56
In thread:69 value of _singletonTest.COUNT = 57
In thread:71 value of _singletonTest.COUNT = 58
In thread:70 value of _singletonTest.COUNT = 59
In thread:72 value of _singletonTest.COUNT = 60
In thread:74 value of _singletonTest.COUNT = 61
In thread:73 value of _singletonTest.COUNT = 62
In thread:75 value of _singletonTest.COUNT = 63
In thread:76 value of _singletonTest.COUNT = 64
In thread:77 value of _singletonTest.COUNT = 65
In thread:78 value of _singletonTest.COUNT = 66
In thread:79 value of _singletonTest.COUNT = 67
In thread:80 value of _singletonTest.COUNT = 68
In thread:81 value of _singletonTest.COUNT = 69
In thread:82 value of _singletonTest.COUNT = 70
In thread:83 value of _singletonTest.COUNT = 71
In thread:84 value of _singletonTest.COUNT = 72
In thread:86 value of _singletonTest.COUNT = 73
In thread:85 value of _singletonTest.COUNT = 74
In thread:87 value of _singletonTest.COUNT = 75
In thread:88 value of _singletonTest.COUNT = 76
In thread:89 value of _singletonTest.COUNT = 77
In thread:90 value of _singletonTest.COUNT = 78
In thread:91 value of _singletonTest.COUNT = 79
In thread:92 value of _singletonTest.COUNT = 80
In thread:93 value of _singletonTest.COUNT = 81
In thread:94 value of _singletonTest.COUNT = 82
In thread:95 value of _singletonTest.COUNT = 83
In thread:96 value of _singletonTest.COUNT = 84
In thread:97 value of _singletonTest.COUNT = 85
In thread:98 value of _singletonTest.COUNT = 86
In thread:99 value of _singletonTest.COUNT = 87
In thread:100 value of _singletonTest.COUNT = 88
In thread:101 value of _singletonTest.COUNT = 89
In thread:102 value of _singletonTest.COUNT = 90
In thread:103 value of _singletonTest.COUNT = 91
In thread:104 value of _singletonTest.COUNT = 92
In thread:105 value of _singletonTest.COUNT = 93
In thread:106 value of _singletonTest.COUNT = 94
In thread:107 value of _singletonTest.COUNT = 95
In thread:108 value of _singletonTest.COUNT = 96
In thread:109 value of _singletonTest.COUNT = 97
In thread:110 value of _singletonTest.COUNT = 98
In thread:111 value of _singletonTest.COUNT = 99
In thread:112 value of _singletonTest.COUNT = 100
Порядок выполнения потоков не гарантирован как-бы изначально, но повторяющихся чисел больше нет.
А вообще вы не синглтон проверяете, а просто потокобезопасность класса в самом классе. Я бы предложил проверять так.
internal class App
{
private readonly ISingletonTest _singletonTest;
public App(ISingletonTest singletonTest)
{
_singletonTest = singletonTest;
}
public void Run()
{
int count = _singletonTest.Increase();
Console.WriteLine("In thread:" + Environment.CurrentManagedThreadId + " value of _singletonTest count = " + count);
}
}
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ISingletonTest, SingletonTest>();
serviceCollection.AddSingleton<App>();
var serviceProvider = serviceCollection.BuildServiceProvider();
for (int i = 0; i < 100; i++)
{
new Thread(() =>
{
var app = serviceProvider?.GetService<App>();
using (var scope = serviceProvider?.CreateScope())
{
app?.Run();
}
}).Start();
}
Console.ReadLine();
Вывод в консоль такой же за исключением того что числа больше перемешаны местами, показывать не буду здесь.
То есть теперь если вы синглтон уберёте.
serviceCollection.AddTransient<ISingletonTest, SingletonTest>();
serviceCollection.AddTransient<App>();
То в консоль выведутся только единички. А в вашем тесте AddTransient ничего не поменяет, потому что App как и её зависимость резолвятся только однократно. При чём, если вы любоё AddTransient поменяете на AddSingleton, то будут числа от 2 до 100, так как в одном случае у вас будет cинглтон App, который отрезолвит свою зависимость один раз, ну потому что он один. Во втором случае все экземпляры App будут резолвить один и тот же синглтон SingletonTest. Наверное это то что вы изначально задумали, и есть, вот так правильно будет тестить, что ISingletonTest - действительно синглтон:
serviceCollection.AddSingleton<ISingletonTest, SingletonTest>();
serviceCollection.AddTransient<App>();

