Async Task.Run в автономном веб-сервисе - это «неправильное использование», пока оно работает? - PullRequest
0 голосов
/ 20 октября 2018

Я работаю над проектом со следующими деталями:

  • Не задействованы IIS или другие сторонние веб-серверы
  • .NET 4.5 Windows-приложение, которое действует как "сервер"
  • Сервер запускает несколько веб-сервисов WCF, все они размещаются самостоятельно
  • Веб-сервисы доступны для нескольких пользователей

Вот наиболее простой пример одного измногие методы веб-сервиса:

public async Task<int> Count()
{
        int result = 0;

        //There is nothing to await here, so I use Task.Run
        await Task.Run(() =>
        {
            using (IDB ctx = new DB())
            {
                result = ctx.Customers.All.Count();
            }
            //Here could happen a lot more
           //System.Threading.Thread.Sleep(10000); 

        }).ConfigureAwait(false);

        return result;
}

Как видите, я использую Task.Run для доступа к некоторым данным, потому что ни один из интерфейсов репозитория не предлагает асинхронных методов.Я не могу ничего ждать.Если бы я хотел выполнить «настоящую асинхронность», мне пришлось бы переписать весь интерфейс хранилища.

Если бы я не использовал Task.Run, сервер блокировал бы все остальные входящие запросы.

Мои 2 вопроса:

  1. Что-то не так с использованием Task.Run в этом сценарии?

  2. Даже еслиэто работает и, возможно, не совсем не так, есть ли лучшее, более профессиональное решение для вызова синхронного кода в асинхронном методе?

Первоначальная причина этого вопроса состоит в том, что я прочитал,что использование Task.Run в асинхронном методе "поддельный асинхронный" .(Я думаю, что Task.Run запускает новый поток, в то время как "real async" код не работает)

Я ответил на свой вопрос, см. Ответ ниже.Я надеюсь, что это может помочь другим.

Ответы [ 2 ]

0 голосов
/ 22 октября 2018

Прежде всего: спасибо всем за подсказки.Мне нужно было, чтобы они глубже погрузились в проблему.

Я нашел реальное решение этой проблемы, и я думаю, я мог бы добавить некоторую ценность сообществу, ответив на свой собственный вопрос подробно.

Решение также можно найти в этой замечательной статье: https://www.oreilly.com/library/view/learning-wcf/9780596101626/ch04s04.html

Вот краткий обзор начальной проблемы и решения:

Цель

  • Моя цель - разместить несколько собственных служб WCF в приложении .NET 4.5
  • Все автономные службы WCF доступны для нескольких клиентов
  • Все самостоятельныеразмещенные службы WCF НЕ ДОЛЖНЫ блокировать друг друга, когда их используют несколько пользователей

Проблема (и ложное решение) (мой первоначальный вопрос)

  • Моя проблема заключалась в том, что всякий раз, когда один клиент использовал веб-сервис, он блокировал другие веб-сервисы, пока не вернулся к клиенту
  • Не важно, какой тип InstanceContextMode или ConcurrencyMode я использовал
  • Myложным решением было использовать async и Task.Run («Fake Async»).Это работало, но это не было реальным решением.

Решение (найдено в статье)

  • При самостоятельном размещении веб-сервисов WCF,вы ДОЛЖНЫ убедиться, что вы всегда вызываете ServiceHost.Open в отдельном потоке, отличном от потока пользовательского интерфейса
  • Каждый раз, когда вы открываете ServiceHost в приложении консоли, WinForms или WPF или в службе Windows, вы должнызнать, в какое время вы вызываете ServiceHost.Open и как вы используете ServiceBehaviorAttribute.UseSynchronizationContext
  • Значением по умолчанию для ServiceBehaviorAttribute.UseSynchronizationContext является True.(это плохо и приводит к блокировке!)
  • Если вы просто вызовете ServiceHost.Open без установки UseSynchronizationContext = false, все ServiceHosts будут выполняться в потоке пользовательского интерфейса и блокировать этот поток и друг друга.

Решение 1 (проверено и работает - больше блокировок нет)

  • Установить ServiceBehaviorAttribute.UseSynchronizationContext = false

Решение 2 (проверено и работает - больше блокировок нет)

  • НЕ трогайте ServiceBehaviorAttribute.UseSynchronizationContext, просто позвольте ему быть истинным
  • Но создайте хотя бы один или несколько потоковв котором вы звоните ServiceHost.Open

Код:

private List<ServiceHost> _ServiceHosts = new List<ServiceHost>();
private List<Thread> _Threads = new List<Thread>(); 

foreach (ServiceHost host in _ServiceHosts)
{
   _Threads.Add(new Thread(() => { host.Open(); }));
   _Threads[_Threads.Count - 1].IsBackground = true;
   _Threads[_Threads.Count - 1].Start();
}

Решение 3 (не проверено, но упоминается в статье)

  • НЕ трогайте ServiceBehaviorAttribute.UseSynchronizationContext, просто позвольте ему быть верным
  • Но убедитесь, что вы вызываете ServiceHost.Open ДО создания потока пользовательского интерфейса
  • Затем ServiceHosts wЯ использую другой поток и не блокирую поток пользовательского интерфейса

Я надеюсь, что это может помочь другим с той же проблемой.

0 голосов
/ 20 октября 2018

Да, это фальшивка async и менее масштабируемая, так как он запускает поток и блокирует его, его нельзя вернуть обратно до его завершения.

Однако

Я называю такие методы «поддельными асинхронными методами», потому что они выглядятасинхронный, но на самом деле просто притворяется, выполняя синхронную работу в фоновом потоке.В общем случае не используйте Task.Run при реализации метода;вместо этого используйте Task.Run для вызова метода.Есть две причины для этого руководства:

  • Потребители вашего кода предполагают, что если метод имеет асинхронную сигнатуру, то он будет действовать действительно асинхронно.Фальсифицировать асинхронность, просто выполняя синхронную работу в фоновом потоке, - удивительное поведение.
  • Если ваш код когда-либо используется в ASP.NET, фальшивый асинхронный метод ведет разработчиков по неверному пути.Цель асинхронности на стороне сервера - масштабируемость, а ложные асинхронные методы менее масштабируемы, чем просто использование синхронных методов.

Идея выставления оболочек «асинхронный по синхронизации» также очень скользкая, чтодоведение до крайности может привести к тому, что каждый отдельный метод будет представлен как в синхронной, так и в асинхронной форме.Многие из тех, кто спрашивает меня об этой практике, рассматривают возможность использования асинхронных оболочек для длительных операций с привязкой к процессору.Намерение хорошее: помочь с отзывчивостью.Но, как было сказано, отзывчивость может быть легко достигнута потребителем API, и потребитель действительно может сделать это на правильном уровне, а не для каждой болтливой отдельной операции.Кроме того, определить, какие операции могут быть длительными, на удивление сложно.Временная сложность многих методов часто значительно различается.

Однако вы на самом деле не вписываетесь ни в одну из этих категорий.Из вашего описания вы размещаете эту услугу WCF.В любом случае он будет выполнять ваш код асинхронно, если вы правильно установили InstanceContextMode и ConcurrencyMode.Кроме того, у вас будет возможность запускать оболочки TBA для вашего звонка из клиента, если вы сгенерировали прокси-серверы с соответствующими настройками.

Если я вас правильно понял, вы можете просто позволить этому методу быть полностью синхронным,и пусть WCF позаботится о деталях и сэкономит ресурсы

Обновление

Пример: если я использую Task.Run внутри любогоМетод веб-службы, я даже могу вызвать Thread.Sleep (10000) внутри Task.Run, и сервер будет реагировать на любой входящий трафик.

Я думаю, что следующее может помочь вам наиболее

Сеансы, экземпляры и параллелизм

Сеанс - это корреляция всех сообщений, отправленных между двумя конечными точками.Экземпляр относится к управлению временем жизни определенных пользователем сервисных объектов и связанных с ними объектов InstanceContext.Параллельность - это термин, который используется для управления количеством потоков, одновременно выполняющихся в InstanceContext.

Похоже, что ваша служба WCF настроена на InstanceContextMode.PerSession и ConcurrencyMode.Single.Если ваша служба не имеет состояния, вы, вероятно, захотите использовать InstanceContextMode.PerCall и использовать async только тогда, когда у вас есть что-то, что действительно можно ожидать

...