Блокировка в IHostedService

#c# #multithreading #asp.net-core

#c# #многопоточность #asp.net-ядро

Вопрос:

У меня есть IHostedService служба-наследник, которая обрабатывает некоторые данные и сохраняет последнюю часть в переменной.

 public class MyService : IHostedService 
{
    private DataType lastData;
    public DataType GetLastData()
    {
       return lastData;
    }

    public void ProcessNextPart()
    {
        ...
    } 
}
 

У меня также есть контроллер api, который использует DI для вызова MyService .
Должен ли я использовать lock или какой-то другой подход lastData в этом случае? Насколько я понимаю, возможно одновременное чтение (с контроллера) и запись (из сервиса) для этой переменной?

Спасибо.

Комментарии:

1. Для чего это MyService нужно? Например IHostedService , он используется для обработки длинной задачи или задачи, которая не связана с запросом. По какой причине вам нужно звонить MyService с контроллера? Чего вы хотите достичь в MyService как размещенной службе и чего вы хотите добиться от контроллера с помощью MyService?

2. MyService не связан с запросом, он выполняет вычисления в фоновом режиме. Клиент может запросить последнюю часть обработанных данных с помощью контроллера. Итак, суть в многопоточном использовании переменной, где хранится эта последняя часть.

3. если DataType это класс, вы в значительной степени в порядке без какого-либо механизма блокировки. Если это структура, то ее запись может быть не атомной, и в этом случае вам следует либо добавить блокировку, либо установить значение

Ответ №1:

Все зависит от того, является ли DataType это классом или структурой.

Если это класс, то его запись является атомарной, так что это в значительной степени безопасно. Если вы считаете, что вызывающий может использовать это значение в цикле, то вы можете добавить изменяемое чтение, чтобы предотвратить кэширование значения в регистре:

 public class MyService : IHostedService 
{
    private DataType lastData;

    public DataType GetLastData()
    {
       return Volatile.Read(ref lastData);
    }

    public void ProcessNextPart()
    {
        lastData = newValue;
    } 
}
 

Но, учитывая вариант использования, это звучит крайне маловероятно.

Если DataType это структура, то это совсем другая история. Если структура больше, чем размер указателя целевой архитектуры (4 байта для x86, 8 байт для x64), то ее запись не является атомарной. Даже если структура достаточно мала, я не рекомендую полагаться на нее, так как позже вы можете добавить больше полей и нарушить этот код. Таким образом, у вас есть два решения: либо использовать блокировку, либо указать значение.

Использование блокировки:

 public class MyService : IHostedService 
{
    private readonly object syncRoot = new object();
    private DataType lastData;

    public DataType GetLastData()
    {
       lock (syncRoot)
           return lastData;
    }

    public void ProcessNextPart()
    {
        lock (syncRoot)
            lastData = newValue;
    } 
}
 

Упаковка значения:

 using System.Runtime.CompilerServices;

public class MyService : IHostedService 
{
    private readonly object syncRoot = new object();
    private StrongBox<DataType> lastData;

    public DataType GetLastData()
    {
        return lastData.Value;
    }

    public void ProcessNextPart()
    {
        lastData = new StrongBox<DataType>(newValue);
    } 
}
 

Комментарии:

1. Спасибо вам за ответ. Является ли первый случай истинным для List<T> и обнуляемым (например, int?)?

2. List<T> это класс, так что это первый случай. Nullable<T> является структурой, поэтому второй случай