2016-09-26 3 views
0

У меня очень странное поведение службы Async. История есть: Там плагин, который стреляет по Lead Create. Целью самого плагина является создание пользовательского перечисления Leads. Плагин получает последний номер из поля в объекте Autonumbering, который хранит числа. Затем плагин увеличивает поле номера Autonumbering entity на 1 и присваивает полученное число Lead.Async Service обрабатывает объект несколько раз

Проблема заключается в следующем: Когда я запускаю массовое создание проводов (испытание на краш для нумерации), например. 400, а счетчик автоматического запуска начинается с 0, когда все проводники обработаны, мой счетчик Autonumbering заканчивается значением ~ 770, что намного больше, чем оценено 400.

Я по опыту обнаружил, что службы Async обрабатывают то же самое несколько раз. Для одних только 1 раз, для других это 2-5 раз.

Почему это происходит?

Вот мой код:

public void Execute(IServiceProvider serviceProvider) 
{ 
    Entity target = ((Entity)context.InputParameters["Target"]); 
    target["new_id"] = GetCurrentNumber(service, LEAD_AUTONUMBER); 
    service.Update(target); 
    return; 
} 

public int GetCurrentNumber(IOrganizationService service, Guid EntityType) 
{ 
    lock (_locker) 
    { 
     Entity record = service.Retrieve("new_autonumbering", EntityType, new ColumnSet("new_nextnumber")); 
     record["new_nextnumber"] = int.Parse(record["new_nextnumber"].ToString()) + 1; 
     service.Update(record); 

     return int.Parse(record["new_nextnumber"].ToString()); 
    } 
} 

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

public class IdAssignerPlugin : IPlugin 
{ 
    private static  IPluginExecutionContext context; 
    private static  IOrganizationServiceFactory factory; 
    private static  IOrganizationService service; 

    public void Execute(IServiceProvider serviceProvider) 
    { 
     context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); 
     factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); 
     service = factory.CreateOrganizationService(null); 
     [...] 
    } 
} 

После комментария @HenkvanBoeijen я понял, что это не безопасный способ, так что я переехал все объявления в Execute() метод.

public class IdAssignerPlugin : IPlugin 
{ 
    public void Execute(IServiceProvider serviceProvider) 
    { 
     IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));; 
     IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));; 
     IOrganizationService service = factory.CreateOrganizationService(null);; 

     [...] 
    } 
} 

Но это не спасло меня от многократной обработки, хотя обработка теперь идет очень быстро.

UPDATE 2: В системе Джобсом я также заметил, что после 11 операций со статусом Retry Count = 0 операции остальные имеют Retry Count = 1, а после 16 лет она Retry Count = 2 и т.д.

(в этом тесте я создал 20 потенциальных клиентов programmaticaly, и после того, как распайке счетчик показывает мне last number = 33, и если я могу суммировать все retry count значения она выходит с 33, который похож на last number в автонумерация)

screenshot

+1

Где 'context' и' 'обслуживания взялся? Похоже, ваш код не является потокобезопасным. –

+0

@HenkvanBoeijen, я ответил в UPDATE 1 в вопросе. –

+0

Повторения, возможно, это рабочий процесс, пытающийся получить блокировку и пропуская ее, вы должны попробовать повторить попытку с помощью Thread.Sleep, поэтому рабочий процесс ждет, чтобы получить блокировку. – dynamicallyCRM

ответ

0

Я нашел проблему. После 11 попыток выполнения всех следующих задач CRM обнаружил ошибку плагина (это было Generic SQL Error без дополнительной информации, и я полагаю, что это может быть вызвано перегрузкой, некоторая SQL Timeout Error).

Событие исполнения трубопровода для CRM является следующее: произошло

  1. событий.
  2. слушателя событий натыкается событием и отправляет его в обработчик, основываясь на следующие параметрах синхронизации асинхронного & Предпускового - послеоперационная (асинхронный - постоперационный в моем случае)
  3. Тогда событие переходит в асинхронном Queue Agent, который решает, когда нужно выполнить плагин.
  4. Асинхронный агент очереди работает с этим плагином событий.
  5. Плагин выполняет свою работу и затем возвращает 0 (например,) при достижении успеха или 1 при неудачной попытке.
  6. Если 0, агент очереди Async закрывает текущий конвейер со статусом Succeeded и отправляет уведомление в ядро ​​CRM.

Ошибка предположительно возникла после обновления Autonumbering внутри кода (этап 5) лица, но до завершения задачи со статусом удалось (шаг 6).

Так из-за этой ошибки CRM снова запускает задачу с теми же InputParameters.

Мой сервер CRM не очень перегружен, поэтому я вышел со следующим обходным:

Я экстраполировать свой замок() заявление о методе всего Execute() и переместить запрос обновления Entity в конец методы ,

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

я выкладываю свой код по историческим причинам:

public class IdAssignerPlugin : IPlugin 
{ 
    public const string AUTONUMBERING_ENTITY = "new_autonumber"; 
    public static Guid LEAD_AUTONUMBER = 
new Guid("yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"); 

    static readonly object _locker = new object(); 

    public void Execute(IServiceProvider serviceProvider) 
    { 
     var context = (IPluginExecutionContext)serviceProvider.GetService(
typeof(IPluginExecutionContext)); 
     var factory = (IOrganizationServiceFactory)serviceProvider.GetService(
typeof(IOrganizationServiceFactory)); 
     var service = factory.CreateOrganizationService(null); 

     if (PreventRecursiveCall(context)) 
     return; 

     lock (_locker) 
     { 
      if (context.InputParameters.Contains("Target") && 
context.InputParameters["Target"] is Entity) 
      { 
       Entity autoNumber; 
       Entity target = ((Entity)context.InputParameters["Target"]); 
       if (target.LogicalName.Equals("lead", 
StringComparison.InvariantCultureIgnoreCase)) 
       { 
        autoNumber = GetCurrentNumber(service, LEAD_AUTONUMBER); 
        target["new_id"] = autoNumber["new_nextnumber"]; 
       } 
       service.Update(autoNumber); 
       service.Update(target); 
      } 
     } 
     return; 
    } 

    public int GetCurrentNumber(IOrganizationService service, Guid EntityType) 
    { 
     Entity record = 
service.Retrieve(AUTONUMBERING_ENTITY, EntityType, new ColumnSet("new_nextnumber")); 
     record["new_nextnumber"] = int.Parse(record["new_nextnumber"].ToString()) + 1; 
     return record; 
    } 

    protected virtual bool PreventRecursiveCall(IPluginExecutionContext context) 
    { 
     if (context.SharedVariables.Contains("Fired")) return true; 
     context.SharedVariables.Add("Fired", 1); 
     return false; 
    } 
} 
+0

Кроме того, это может помочь контролировать Retries: http://develop1.net/public/post/Control-Async-Workflow-Retries.aspx –

0

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

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

/// <summary> 
/// Allows Plugin to trigger itself. Delete Messge Types always return False 
/// since you can't delete something twice, all other message types return true 
/// if the execution key is found in the shared parameters. 
/// </summary> 
/// <param name="context"></param> 
/// <returns></returns> 
protected virtual bool PreventRecursiveCall(IExtendedPluginContext context) 
{ 
    if (context.Event.Message == MessageType.Delete) 
    { 
     return false; 
    } 

    var sharedVariables = context.SharedVariables; 
    var key = $"{context.PluginTypeName}|{context.Event.MessageName}|{context.Event.Stage}|{context.PrimaryEntityId}"; 
    if (context.GetFirstSharedVariable<int>(key) > 0) 
    { 
     return true; 
    } 

    sharedVariables.Add(key, 1); 
    return false; 
} 
+0

К сожалению, это не решает мою проблему. Также см. «Обновление 2» в вопросе выше. –

Смежные вопросы