Jeer
@Jeer
уверенный пользователь

Как правильно варить многопоточность на тасках?

Добрый день!
Хочу разобраться как правильно работать с многопоточностью на основе класса Task.
К сожалению, в интернете практически все примеры либо Task.Factory.StartNew, либо связка: эти же таски пихаются в массив и над ним делается Task.WaitAll.
Меня же интересует ситуация, когда нужно считать данные из базы в многопоточном (не в асинхронном) режиме;
Создаем модель, в которую поместим результат:
/// <summary>
    /// Информация о разделах
    /// </summary>
    public class ImgStatisticDto
    {
        /// <summary>
        /// Раздел
        /// </summary>
        public enImg Section { get; set; }

        /// <summary>
        /// Количество не готовых к использованию фото
        /// </summary>
        public int NotReady { get; set; }

        /// <summary>
        /// Общее количество фотографий в разделе
        /// </summary>
        public int Count { get; set; }

        public ImgStatisticDto() { }

        public ImgStatisticDto(enImg section)
        {
            Section = section;
        }
    }


И пишем метод, который возвращает результат, например:
private ImgStatisticDto GetStatistic(enImg section)
        {
            var res = new ImgStatisticDto(section);

            using (var db = ImagesContextFactory.Create(section))
            {
                var count = db.Image.CountAsync();
                var notReady = db.Image.Where(x => !x.IsReady).CountAsync();
                Task.WaitAll(count, notReady);
                res.Count = count.Result;
                res.NotReady = notReady.Result;
            }

            return res;
        }

То есть мы делаем несколько запросов к базе, пихаем их в Task.WaitAll и результаты заносим в возвращаемый тип.
Это довольно распространенная ситуация и так можно писать, выглядит сносно. По этой же схеме можно в контроллере дёргать async методы из не связанных сервисов, делать WaitAll, заполнять результат и выплёвывать полученные данные.

Разберем далее этот пример, к примеру, у меня есть несколько разделов у изображений (мои енумы) и я хочу получить такую статистику по этим разделам.
Я пишу что-то вроде:
public async Task<IList<ImgStatisticDto>> GetStatistic()
        {
            var res = new List<ImgStatisticDto>();
            foreach(enImg section in Enum.GetValues(typeof(enImg)))
            {
                if (section == enImg.none) continue;
                res.Add(GetStatistic(section));
            }

            return res;
        }

Дело в том, что в этом методе слова async и Task не работают. Метод GetStatistic он как бы синхронный. И в цикле будет синхронное выполнение, не смотря на то, что внутри метода запросы будут выполняться в многопоточном режиме.
А я хочу, чтобы и запуск этих методов был так же в многопоточном режиме. Только не могу понять, как это красиво написать.
  • Вопрос задан
  • 249 просмотров
Решения вопроса 1
@kttotto
пофиг на чем писать
Вы ошибаетесь. У Вас нет параллельного выполнения задач в методе GetStatistic().
Вот это count.Result равносильно
count = db.Image.CountAsync().Result
Это обычное синхронное выполнение.

Вот так Task.WaitAll(count, notReady); Вы запустили задачи параллельно, но результат из них так не получите.
Если в цикле хотите тело цикла выполнять в разных потоках, используйте Parallel.ForEach
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@CHolfield
попробуй код внутри таска обернуть так, и тогда многопоточностью и очередностью запросов займется СУБД:
public async Task<IList<ImgStatisticDto>> GetStatistic()
        {
            var res = new List<ImgStatisticDto>();
using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            foreach(enImg section in Enum.GetValues(typeof(enImg)))
            {
                if (section == enImg.none) continue;
                res.Add(GetStatistic(section));
            }
            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
        }
    }
return res;
        }
Ответ написан
petermzg
@petermzg
Самый лучший программист
Одно лишь использование Task не подразумевает многопоточность.
Тут приведен пример доказывающий это. И эта статья поможет разобраться.
Ответ написан
yarosroman
@yarosroman Куратор тега C#
C# the best
Как вам сказали, Task это не многопоточность, для работы с потоками используйте Thread.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы