2017-02-02 15 views
1

У меня есть функция async, которая будет работать и выполнять долгое время и интенсивную работу с ЦП. Поскольку это асинхронно, он не поддерживает пользовательский интерфейс.Один длительный процесс для каждого пользователя в приложении ASP.NET Core

В нормальных условиях у меня нет проблем с тем, как он работает. Процесс запускается нажатием кнопки, но это может быть вызвано вызовом API.

Я обнаружил, что если я нажимаю кнопку много раз подряд, весь сайт начинает реагировать медленно. Это злоупотребление, но тем не менее оно оказывает пагубное влияние на производительность.

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

Кто знает какой-то умный способ реализации этого или сделал что-то подобное раньше. Я думаю о чем-то вроде потоковой коллекции, к которой вы можете добавить задачи, но если вы попытаетесь. Запустите задачу, которая уже запущена для определенного пользователя/Claimprincipal, тогда она не сможет ее добавить.

+1

https://msdn.microsoft.com/en-us/magazine/dn802603.aspx Прочтите это, почему запуск ** CPU bound ** асинхронные задачи - это ужасная идея в ASP.NET (также применяется к ASP.NET ядро)! Используйте async только для действительно асинхронных операций, таких как соединения I/O (File, Network) или Database. Никогда для задач с привязкой к ЦП. Делая ресурсоемкие вещи в фоновом потоке или задачи полезно только для разработки пользовательского интерфейса (приложения WPF, Windows Phone и т.д.), где пользовательский интерфейс будет заблокирован, если вы запускаете ресурсоемкие вещи на UI теме – Tseng

ответ

0

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

Вы можете использовать коллекцию потоковых сетей для хранения флага для каждого пользователя, который указывает, выполняется ли в данный момент такой процесс. Используйте блок try {} finally {} в вашем асинхронном коде для управления значением. Установите его в true в блоке try и установите в нем значение false. Остальная часть вашей длительной работы приходит после того, как вы установите ее в true и перед вами, наконец, будет выполнена. набросал ниже:

try{ 
    //if thread safe value for this user is already true then exit. 

    //set thread safe collection value for this user to true 

    //Do work 

}finally{ 

    //set thread safe collection value for this user to false; 
} 

Если бы это было, я бы поместить объект в коллекции, где объект имеет одно значение которое было BOOL. Таким образом, я мог бы использовать ключевое слово lock, чтобы удерживать блокировку объекта для этого пользователя, когда я проверяю его значение или устанавливаю его значение, чтобы я мог убедиться, что ни один другой процесс не изменил значение между тем, когда я его проверил, и когда я его установил ,

Надеюсь, что это поможет.

+0

Это тип ответа, который я искал. Есть ли способ иметь набор запущенных задач вместе с некоторым идентификатором пользователя, который я могу использовать, чтобы определить, имеет ли этот пользователь уже одну из этих задач, поэтому я могу помешать им выдавать другую, пока их текущий работает. Какой-то словарь или что-то в этом роде? (int is userId) – ravetroll

+0

Уверен, что вы могли бы это сделать, но нет необходимости ставить задачу в словаре, если вы просто хотите узнать, работает ли она для пользователя. Просто поместите объект в словарь для userId, и в этом объекте есть bool, который вы установите или очистите, как я изложил выше. Этот bool сообщает вам, работает ли у пользователя обработка или нет. Вы можете поместить bool прямо в словарь, но преимущество объекта, содержащего bool, заключается в том, что вы можете заблокировать объект, пока вы проверяете его значение и/или можете изменить его значение. –

3

Есть некоторые проблемы с вашим решением:

  1. Если задача ресурсоемкие он все еще может вместить до UI, даже если это async. Когда все процессоры заняты, вы увидите влияние производительности.
  2. Долгосрочные taks - не очень хорошая идея на веб-сервере. Веб-серверы предназначены для обработки большого количества коротких запросов. Длинные запросы не обрабатываются эффективно, и они также могут быть переработаны до их завершения.
  3. Ваша реализация упрощает атаки на отказ в обслуживании. Вы в основном создали один, нажав кнопку несколько раз.

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

Используйте форму связи между сервером веб-приложений и приложениями, которая наилучшим образом соответствует вашим потребностям: WCF, REST API или инфраструктура очередей сообщений, такая как MSMQ или RabbitMQ.

+0

Я слышу вас. То, что я пытаюсь сделать, это остановить злостное обращение к не часто используемой функции от траты ресурсов. Даже если задача была запущена где-то, кроме веб-сервера, все равно потенциально приведет к сканированию этого другого сервера. Задача не так долго работает, но если я нажимаю кнопку так быстро, как могу на минутку (несколько кликов в секунду), это вызывает проблемы с производительностью. – ravetroll

+0

В зависимости от вашей технологии сервер приложений может справиться с этим. Если, например, ваши задачи отправляются в очередь, где старые записи могут истечь, сервер приложений не будет перегружен. Все зависит от ваших требований. – Sefe

+0

Я использую Hangfire для запуска некоторых легких фоновых задач и его блестящего. Задачи выполняются за несколько миллисекунд, поэтому я не злоупотребляю им. – ravetroll

0

В конце концов я реализовал службу как Singleton, что я зарегистрирован в DI.

public class SingleInstanceRegisterService: IDisposable 
{ 
    private ConcurrentDictionary<SingleInstance,SingleInstance> dict = new ConcurrentDictionary<SingleInstance, SingleInstance>(); 
    AutoResetEvent arv; 
    Timer timer; 

    public SingleInstanceRegisterService() 
    { 
     arv = new AutoResetEvent(false); 
     timer = new Timer(this.Cleanup, arv, 0, 60000); 
    } 

    public bool TryAdd(SingleInstance i) 
    { 
     return dict.TryAdd(i, i); 
    } 

    public bool ContainsKey(SingleInstance i) 
    { 
     return dict.ContainsKey(i);    
    } 

    public bool TryRemove(SingleInstance i) 
    { 
     SingleInstance x; 
     return dict.TryRemove(i, out x); 
    } 

    public void Cleanup(Object stateInfo) 
    { 
     AutoResetEvent autoEvent = (AutoResetEvent)stateInfo; 

     if (dict != null) 
     { 
      foreach(SingleInstance i in dict.Keys) 
      { 
       if (i.Expiry < DateTimeOffset.Now) 
       { 
        TryRemove(i); 
       } 
      } 

     } 
     autoEvent.Set(); 
    } 

    public void Dispose() 
    { 
     timer?.Dispose(); 
     timer = null; 
    } 
} 

SingleInstance затем определяется как:

public class SingleInstance: IEquatable<SingleInstance> 
{ 
    public string UserSubject; 
    public string FullMethodName; 
    public DateTimeOffset Expiry; 

    public SingleInstance(User u, MethodInfo m, DateTimeOffset exp) 
    { 
     UserSubject = u.UserSubject.ToString(); 
     FullMethodName = m.DeclaringType.FullName + "." + m.Name; 
     Expiry = exp; 
    } 

    public bool Equals(SingleInstance other) 
    { 
     return (other != null && 
      other.FullMethodName == this.FullMethodName && 
      other.UserSubject == this.UserSubject); 
    } 

    public override bool Equals(object other) 
    { 
     return this.Equals(other as SingleInstance); 
    } 

    public override int GetHashCode() 
    { 
     return (UserSubject.GetHashCode() + FullMethodName.GetHashCode()); 
    } 
} 

Чтобы использовать его мы следующее. _single - моя зависимая инъекция SingleInstanceRegisterService.

var thisInstance = new SingleInstance(user, method, DateTimeOffset.UtcNow.AddMinutes(1)); 
if (_single.TryAdd(thisInstance)) 
{ 
    // tell the database to kick off some long running process 
    DoLongRunningDatabaseProcess(); 
    // remove lock on running process 
    _single.TryRemove(thisInstance); 
} 

Если TryAdd не смог его добавить, он должен быть запущен. Обратите внимание, что есть таймер, который автоматически очистит блокировки процессов, срок действия которых истек.