Calculate не является асинхронной функцией поэтому async не нужен. И очень плохо вызывать в асинхронном коде Thread.Sleep, для асинхронного кода есть Task.Delay.
async преобразует код в конечный автомат (State Machine)
Вы наверное знаете, что существует лимит на кол-во подключений к одному хосту. По умолчанию 2 конекта для обычных приложений и 10 для приложений запущенных как ASP.NET host. ServicePointManager.DefaultConnectionLimit
- Использование потоков.
- Использование тасков и особенности работы с ними.
- Синхронизация доступа к данным.
- для UI приложений, особенности доступа к контролам из потоков.
Переключение контекста сохраняет все регистры процессора для потока и востанавливает для активного.
Если у вас идет обращение к переменной через регистр (см. volatile), а не напрямую к адресу памяти, то сохранность актуального значения не гарантируется. Примерный сценарий:
1. Первый поток поместил значение переменной в регистр (mov ax, [4C00h])
2. Второй поток сохранил его в регистр (mov ax, [4C00h])
3. Первый сделал какие-то операции над значением в регистре
4. Второй сделал.
5. Первый сохраняет значение по адресу памяти (mov [4C00h], ax)
6. Второй.
И вот какое значение сохранилось? Ведь каждый поток оперировал своими копиями регистров. И четкая последовательность вызова потоков тоже не гарантируется.
Есть управляемые dll, это которые на языках .net написаны, те вы подключаете к проекту using. А есть dll, что написаны для win32. Вот их вы и подключаете через [DllImport("...")]
Так все варианты приемлемы, применяйте тот, что нравится:
1. SQL Server 2017/2019 на linux
2. Удаленно к Azure SQL Database, или иным подобным
3. Postgresql, Oracle, MySQL и т.д.
Очень много крупных проектов пишутся на .net core. Лично принимал участие в 4х за 3 года. Из них только один хостится на windows сервере, остальные на linux.