Мы наблюдаем некоторые трудно воспроизводимые проблемы, которые, по-видимому, связаны с сервером SQL и тем, как мы с ним взаимодействуем, и я выясняю, можем ли мы использовать DBContext в сочетании с Task неправильно.
У нас есть концентратор signalR, который использовал множество моих клиентов. Когда мы достигаем определенного уровня клиентов, система замедляется, и в конечном итоге все перестает работать.
signalR похож на этот пример, но с гораздо большим количеством асинхронных методов:
namespace Test.Webservice.Hubs
{
public class ExampleHub : Hub<ExampleClientContract>, IExampleHub
{
private readonly ILogger _logger;
private readonly IExampleRepository _exampleRepo;
public ExampleHub(ILogger logger, IExampleRepository exampleRepo)
{
_logger = logger;
_exampleRepo = exampleRepo;
}
public async Task<IEnumerable<ExampleTopicState>> GetExampleStates(Guid operationId)
{
var examples = await _exampleRepo.GetExampleStatesAsync(operationId);
return examples.Select(ExampleTopicState.Create);
}
}
}
ExampleRepository выглядит примерно так, но опять же с большим количеством методов:
namespace Test.DataAccess.Repository
{
public class ExampleRepository : IExampleRepository
{
private readonly ILogger _logger;
private readonly ExampleContext _context;
private readonly IExampleRepositoryMapping _repositoryMapping;
public ExampleRepository(ILogger logger, IExampleRepositoryMapping repositoryMapping)
{
_repositoryMapping = repositoryMapping;
_logger = logger;
_context = new ExampleContext();
}
public async Task<IEnumerable<ExampleDto>> GetExampleStatesAsync(Guid operationId)
{
IEnumerable<Example> result = null;
await Task.Factory.StartNew(() =>
{
result = _context
.Examples.Where(c => c.OperationId.Equals(operationId))
.GroupBy(o => o.Type)
.SelectMany(p => p.GroupBy(q => q.Topic))
.Select(o => o.FirstOrDefault(
n => n.ExampleTimeUtc == o.Max(d => d.ExampleTimeUtc)));
});
return _repositoryMapping.Mapper.Map<IEnumerable<ExampleDto>>(result);
}
}
}
Правильно ли использовать DbContext внутри ожидания Task.Factory.StartNew или это может привести к проблемам? Этот SO answere кажется, говорит, что это может быть проблематично, но я не уверен, что понимаю это полностью.
Редактировать:
Мысль добавить пример того, как хаб вызывается с одного из клиентов:
var loadingTasks = _connectorMap.Select(o => (System.Action) ( () =>
{
var result = o.Proxy.Invoke<ExampleResult>(
"GetExampless",
_connectedOperations.Where(c => o.HubConnection.Url.StartsWith(c.ExampleWebService)).Select(s => s.UniqueId),
filter.TimeFilter.Start,
filter.TimeFilter.End,
filter.TopicFilter,
filter.SeverityLevelFilter,
currentPage, pageSize,
filter.IsGrouped).Result;
_results.Add(result);
}));
Parallel.ForEach(loadingTasks, lt => lt());
Task.Factory.StartNew
= "пожалуйста, запустите этот код в каком-нибудь другом потоке для меня, у меня есть другие дела" - await
- "У меня нет никакой полезной работы, пока эта другая вещь не будет завершена". Вы видите, как они в основном противоречивы? Если ваш текущий поток не является чем-то особенным, просто запустите код в существующем потоке.
@Damien_The_Unbeliever Хороший вопрос. Может ли это быть причиной проблем, которые мы наблюдаем?
@devNull Он зарегистрирован следующим образом: container.Register(Component.For<IExampleRepository>().ImplementedBy<ExampleRepository>().LifestyleTransient());
Вы просто хотите SelectAsync?
public async Task<IEnumerable<ExampleDto>> GetExampleStatesAsync(Guid operationId)
{
IEnumerable<Example> result = null;
var result = await _context
...
.SelectMany(...);
.Select(...)
.ToListAsync(); // Fetch from db.
});
return _repositoryMapping.Mapper.Map<IEnumerable<ExampleDto>>(result);
}
Полный пример доступен по адресу Асинхронный запрос и сохранение.
Спасибо за это! Как вы думаете, дизайн с использованием Task может привести к проблемам или он просто неэффективен?
Я думаю, что наиболее подходящим прилагательным является то, что Task.Factory.StartNew
не нужен. Даже в тех случаях, когда нет готового метода (например, ToListAsync), Task.Run — это путь: см. документы:* Начиная с .NET Framework 4.5, метод Task.Run является рекомендуемым запустить задачу, связанную с вычислениями.*
Кроме того, вы смешивали Task
с IEnumerable<T>
, что не обязательно неправильно, но, безусловно, может сбивать с толку. StartNew
(который должен быть Task.Run
) отправляет работу в поток пула потоков, но отталкиваемая работа буквально просто создает запрос, даже не запуская его, потому что IEnumerable<T>
выполняется лениво. Использование ToListAsync
будет правильно выполнять запрос асинхронно. И тогда поток пула потоков вообще не нужен, так как он уже асинхронный.
Оказывается, проблема была где-то еще в коде. Спасибо за указание на проблемные части кода. Очень признателен!
@ Стивен Клири Это имеет смысл! Я был сбит с толку тем, почему IEnumeral использовался сам. Это код, написанный разработчиками, которые больше не работают в компании, поэтому спросить об этом не у кого.
Каков срок службы
IExampleRepository
(т.е. как он регистрируется в DI)?