Поделиться через


Платформа заданий таймера PnP

Платформа заданий таймера PnP — это набор классов, предназначенных для упрощения создания фоновых процессов, которые работают на сайтах SharePoint. Платформа заданий таймера аналогична локальным заданиям таймера кода полного доверия (SPJobDefinition). Основное различие между платформой задания таймера и заданием таймера кода полного доверия заключается в том, что платформа заданий таймера использует только клиентские API и поэтому может (и должна) выполняться за пределами SharePoint. Платформа заданий таймера позволяет создавать задания таймера, которые работают с SharePoint Online.

После создания задания таймера его необходимо запланировать и выполнить. Ниже приведены два наиболее распространенных варианта.

  • Если Microsoft Azure является платформой размещения, задания таймера можно развертывать и запускать как веб-задания Azure.
  • Если Windows Server является платформой размещения (например, для локальной среды SharePoint), задания таймера можно развертывать и запускать в планировщике Windows.

Общие сведения о заданиях таймера см. в видео Общие сведения о платформе заданий таймера PnP, в котором представлена платформа заданий таймера и демонстрируется простой пример задания таймера.

Пример простого задания таймера

В этом разделе вы узнаете, как создать очень простое задание таймера. Цель этого примера — предоставить читателю быстрое представление. позже мы предоставим более подробное описание платформы заданий таймера.

Примечание.

Более обширное решение PnP с десятью отдельными примерами заданий таймера, от примеров "Hello world" до фактических заданий окончания срока действия содержимого, см. в разделе Core.TimerJobs.Samples.

Ниже описано, как создать простое задание таймера.

Шаг 1. Создание консольного проекта и ссылка на PnP Core

Создайте проект типа console и создайте ссылку на библиотеку PnP Core, выполнив одно из следующих действий:

  • Добавьте в проект пакет NuGet Office 365 Developer Patterns and Practices Core. Существует пакет NuGet для версии 15 (локально) и для версии 16 (Office 365). Это предпочтительный способ.

  • Добавьте в проект существующий исходный проект PnP Core. Это позволяет выполнить шаг в базовом коде PnP при отладке.

    Примечание.

    Вы несете ответственность за обновление этого кода с учетом последних изменений, добавленных в PnP.

Шаг 2. Создание класса задания таймера и добавление логики задания таймера

  1. Добавьте класс для задания таймера с именем SimpleJob.

  2. Класс наследует абстрактный базовый класс TimerJob .

  3. В конструкторе присвойте заданию таймера имя (base("SimpleJob")) и подключите обработчик событий TimerJobRun .

  4. Добавьте логику задания таймера в обработчик событий TimerJobRun .

Результат будет выглядеть примерно так:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using OfficeDevPnP.Core.Framework.TimerJobs;

namespace Core.TimerJobs.Samples.SimpleJob
{
    public class SimpleJob: TimerJob
    {
        public SimpleJob() : base("SimpleJob")
        {
            TimerJobRun += SimpleJob_TimerJobRun;
        }

        void SimpleJob_TimerJobRun(object sender, TimerJobRunEventArgs e)
        {
            e.WebClientContext.Load(e.WebClientContext.Web, p => p.Title);
            e.WebClientContext.ExecuteQueryRetry();
            Console.WriteLine("Site {0} has title {1}", e.Url, e.WebClientContext.Web.Title);
        }
    }
}

Шаг 3. Обновление Файла Program.cs для использования задания таймера

Задание таймера, созданное на предыдущем шаге, по-прежнему необходимо выполнить. Для этого обновите Файл Program.cs, выполнив следующие действия:

  1. Создайте экземпляр класса задания таймера.

  2. Укажите сведения о проверке подлинности для задания таймера. В этом примере для проверки подлинности в SharePoint Online используются имя пользователя и пароль.

  3. Добавьте один или несколько сайтов для программы задания таймера для доступа. В этом примере используется дикий символ карта в URL-адресе. Задание таймера выполняется на всех сайтах, которые соответствуют этому url-адресу дикого карта.

  4. Запустите задание таймера, вызвав метод Run .

static void Main(string[] args)
{
    // Instantiate the timer job class
    SimpleJob simpleJob = new SimpleJob();

    // The provided credentials need access to the site collections you want to use
    simpleJob.UseOffice365Authentication("user@tenant.onmicrosoft.com", "pwd");

    // Add one or more sites to operate on
    simpleJob.AddSite("https://<tenant>.sharepoint.com/sites/d*");

    // Run the job
    simpleJob.Run();
}

Параметры развертывания задания таймера

На предыдущем шаге демонстрируется простое задание таймера. Следующий шаг — развертывание задания таймера.

Задание таймера — это файл .exe, который должен быть запланирован на платформе размещения. В зависимости от выбранной платформы размещения развертывание отличается. В следующих разделах описаны два наиболее распространенных варианта платформы размещения:

  • Использование Azure в качестве платформы размещения.
  • Использование Windows Server в качестве платформы размещения.

Развертывание заданий таймера в Azure с помощью веб-заданий Azure

Перед развертыванием задания таймера убедитесь, что задание может выполняться без вмешательства пользователя. Пример в этой статье предлагает пользователю ввести пароль или секрет клиента (см. дополнительные сведения в разделе Проверка подлинности ), который работает во время тестирования, но не работает при развертывании. Все существующие примеры позволяют пользователю указать пароль или секрет клиента с помощью файла app.config:

  <appSettings>
    <add key="user" value="user@tenant.onmicrosoft.com"/>
    <add key="password" value="your password goes here!"/>
    <add key="domain" value="Contoso"/>
    <add key="clientid" value="a4cdf20c-3385-4664-8302-5eab57ee6f14"/>
    <add key="clientsecret" value="your clientsecret goes here!"/>
  </appSettings>

После добавления этих изменений в файл app.config запустите задание таймера из Visual Studio, чтобы убедиться, что оно выполняется без вмешательства пользователя.

Фактическое развертывание в Azure основано на веб-заданиях Azure. Чтобы развернуть этот пример задания таймера, выполните следующие действия.

  1. Щелкните правой кнопкой мыши проект в Visual Studio и выберите Опубликовать как веб-задание Azure.

  2. Укажите расписание задания таймера и нажмите кнопку ОК.

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

  4. Выберите Опубликовать , чтобы отправить веб-задание в Azure.

  5. После публикации задания таймера можно активировать задание и проверка его выполнение из Visual Studio или портал Azure.

    портал Azure (устаревшая версия)

  6. Кроме того, задание таймера можно запустить из новой портал Azure, выбрав задание и выбрав Выполнить. Дополнительные сведения о работе с веб-заданиями на новом портале см. в статье Выполнение фоновых задач с помощью веб-заданий в Служба приложений Azure.

    портал Azure (текущий)

Примечание.

Подробные рекомендации по развертыванию веб-задания Azure см. в статье Начало работы с веб-заданиями Azure (заданиями таймера) для сайтов Office 365.

Развертывание заданий таймера в Windows Server с помощью планировщика Windows

При развертывании в Windows Server задание таймера должно выполняться без вмешательства пользователя.

  1. Измените файл app.config, как описано в предыдущем разделе Развертывание заданий таймера в Azure с помощью веб-заданий Azure.

  2. Скопируйте выпускную версию задания на сервер, на который оно будет работать.

    Важно!

    Скопируйте все соответствующие сборки, файл .exe и файл .config, чтобы задание можно было запустить на сервере без установки дополнительных файлов или программ на сервере.

  3. Запланируйте выполнение задания таймера. Рекомендуется использовать встроенный планировщик задач Windows. Чтобы использовать планировщик задач Windows, выполните следующие действия:

    1. Откройте планировщик задач (панель управления>Назначить планировщик).
    2. Выберите Создать задачу и укажите имя и учетную запись, которая будет выполнять задачу.
    3. Выберите Триггеры и добавьте новый триггер. Укажите нужное расписание для задания таймера.
    4. Выберите Действия и выберите действие Запуск программы, выберите задание таймера .exe файл, а затем задайте начальное значение в папке.
    5. Нажмите кнопку ОК , чтобы сохранить задачу.

Планировщик задач Windows

Подробные сведения о платформе заданий таймера

В этом разделе описаны функции платформы заданий таймера и их работа.

Структура

Класс TimerJob является абстрактным базовым классом, который содержит следующие открытые свойства, методы и события:

Структура класса TimerJob

Большинство свойств и методов более подробно описаны в следующих разделах. Остальные свойства и методы описаны здесь:

  • Свойство IsRunning: возвращает значение, указывающее, выполняется ли задание таймера. Значение true при выполнении; значение false , если не выполняется.
  • Свойство Name . Возвращает имя задания таймера. Имя изначально задается в конструкторе задания таймера.
  • Свойство SharePointVersion: возвращает или задает версию SharePoint. Это свойство задается автоматически на основе версии загруженного Microsoft.SharePoint.Client.dll и в целом не должно изменяться. Однако это свойство можно изменить, если вы хотите использовать библиотеки CSOM версии 16 в развертывании версии 15 (локально).
  • Свойство Version : возвращает версию этого задания таймера. Версия изначально устанавливается в конструкторе задания таймера или по умолчанию — 1.0, если не задано с помощью конструктора.

Чтобы подготовиться к выполнению задания таймера, необходимо сначала настроить его:

  1. Укажите параметры проверки подлинности .
  2. Укажите область, который представляет собой список сайтов.
  3. При необходимости задайте свойства задания таймера.

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

  1. Разрешение сайтов. URL-адреса сайтов wild карта (например, https://tenant.sharepoint.com/sites/d*) разрешаются в фактический список существующих сайтов. Если было запрошено расширение дочернего сайта, список разрешенных сайтов расширяется со всеми дочерними сайтами.
  2. Создайте пакеты работ на основе текущих параметров протектора и создайте по одному потоку на пакет.
  3. Потоки выполняют рабочие пакеты и вызывают событие TimerJobRun для каждого сайта в списке.

Дополнительные сведения о каждом шаге см. в следующих разделах.

Проверка подлинности

Прежде чем можно будет использовать задание таймера, задание таймера должно знать, как выполнить проверку подлинности обратно в SharePoint. В настоящее время платформа поддерживает подходы в перечислении AuthenticationType : Office365, NetworkCredentials и AppOnly. Использование следующих методов также автоматически задает для свойства AuthenticationType соответствующее значение Office365, NetworkCredentials и AppOnly.

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

Блок-схема шагов проверки подлинности

Учетные данные пользователя

Чтобы указать учетные данные пользователя для запуска в Office 365, можно использовать два метода:

public void UseOffice365Authentication(string userUPN, string password)
public void UseOffice365Authentication(string credentialName)

Первый метод принимает имя пользователя и пароль. Второй позволяет указать универсальные учетные данные, хранящиеся в диспетчере учетных данных Windows. На следующем снимку экрана показаны универсальные учетные bertonline данные. Чтобы использовать его для проверки подлинности задания таймера, укажите bertonline в качестве параметра второго метода.

Диспетчер учетных данных Windows


Существуют аналогичные методы для запуска в локальной среде SharePoint:

public void UseNetworkCredentialsAuthentication(string samAccountName, string password, string domain)
public void UseNetworkCredentialsAuthentication(string credentialName)

Только приложение

Проверка подлинности только для приложений является предпочтительным методом , так как вы можете предоставить разрешения на уровне клиента. Для учетных данных пользователя учетная запись пользователя должна иметь необходимые разрешения.

Примечание.

Определенная логика разрешения сайта не будет работать с проверкой подлинности только для приложений. Подробные сведения см. в следующем разделе.

Чтобы настроить задание для проверки подлинности только для приложений, используйте один из следующих методов:

public void UseAppOnlyAuthentication(string clientId, string clientSecret)
public void UseAzureADAppOnlyAuthentication(string clientId, string clientSecret)

Тот же метод можно использовать для Office 365 или локальной среды SharePoint, что упрощает перенос заданий таймера, использующих проверку подлинности только для приложений.

Примечание.

При использовании проверки подлинности только для приложений логика задания таймера завершается сбоем, если используются API, которые не работают с AuthenticationType.AppOnly. Типичные примеры — ЭТО API поиска, запись в хранилище таксономии и использование API профиля пользователя.

Сайты для работы

При выполнении задания таймера требуется один или несколько сайтов.

Добавление сайтов в задание таймера

Чтобы добавить сайты в задание таймера, используйте следующий набор методов:

public void AddSite(string site)
public void ClearAddedSites()

Чтобы добавить сайт, укажите полный URL-адрес (например, https://tenant.sharepoint.com/sites/dev) или URL-адрес дикого карта.

Дикий URL-адрес карта — это URL-адрес, заканчивающийся звездочкой (*). Допускается только один из * них, и он должен быть последним символом URL-адреса. Пример URL-адреса дикого карта — , https://tenant.sharepoint.com/sites/*который возвращает все семейства веб-сайтов по управляемому пути этого сайта. В другом примере возвращает все семейства веб-сайтов, https://tenant.sharepoint.com/sites/dev* в которых URL-адрес содержит dev.

Как правило, сайты добавляются программой, которая создает экземпляр объекта задания таймера, но при необходимости задание таймера может управлять переданным списком сайтов. Для этого добавьте переопределение метода для виртуального UpdateAddedSitesметода, как показано в следующем примере:

public override List<string> UpdateAddedSites(List<string> addedSites)
{
    // Let's assume we're not happy with the provided list of sites, so first clear it
    addedSites.Clear();

    // Manually adding a new wildcard Url, without an added URL the timer job will do...nothing
    addedSites.Add("https://bertonline.sharepoint.com/sites/d*");

    // Return the updated list of sites
    return addedSites;
}

Указание учетных данных перечисления

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

Чтобы получить список семейств веб-сайтов, платформа таймера ведет себя по-разному в Office 365 (версия 16) и локально (версия 15):

  • Office 365. Метод Tenant.GetSiteProperties используется для чтения "обычных" семейств веб-сайтов; API поиска используется для чтения OneDrive для бизнеса семейств веб-сайтов.
  • Локальная среда SharePoint: API поиска используется для чтения всех семейств веб-сайтов.

Учитывая, что API поиска не работает с контекстом пользователя, задание таймера возвращается к указанным учетным данным перечисления.

Чтобы указать учетные данные пользователя для запуска в Office 365, можно использовать два метода:

public void SetEnumerationCredentials(string userUPN, string password)
public void SetEnumerationCredentials(string credentialName)

Существуют аналогичные методы для запуска в локальной среде SharePoint:

public void SetEnumerationCredentials(string samAccountName, string password, string domain)
public void SetEnumerationCredentials(string credentialName)

Первый метод просто принимает имя пользователя, пароль и при необходимости домен (в локальной среде). Второй указывает универсальные учетные данные, хранящиеся в диспетчере учетных данных Windows. Дополнительные сведения о диспетчере учетных данных см. в разделе Проверка подлинности .

Расширение дочернего сайта

Часто требуется, чтобы код задания таймера выполнялся на корневом сайте семейства веб-сайтов и на всех дочерних сайтах этого семейства веб-сайтов. Для этого задайте для свойства ExpandSubSitesзначение true. Это приводит к тому, что задание таймера развертывает дочерние сайты в рамках этапа разрешения сайта.

Переопределение разрешенных и (или) развернутых сайтов

После того как платформа таймера разрешает дикие карта сайты и при необходимости расширяет дочерние сайты, следующим шагом является обработка списка сайтов. Перед обработкой списка сайтов может потребоваться изменить список сайтов. Например, может потребоваться удалить определенные сайты или добавить дополнительные сайты в список. Это можно сделать, переопределив виртуальный метод ResolveAddedSites . В следующем примере показано, как переопределить метод ResolveAddedSites , чтобы удалить один сайт из списка.

public override List<string> ResolveAddedSites(List<string> addedSites)
{
    // Use default TimerJob base class site resolving
    addedSites = base.ResolveAddedSites(addedSites);

    //Delete the first one from the list...simple change. A real life case could be reading the site scope
    //from a SQL (Azure) DB to prevent the whole site resolving.
    addedSites.RemoveAt(0);

    //Return the updated list of resolved sites...this list will be processed by the timer job
    return addedSites;
}

Событие TimerJobRun

Платформа заданий таймера разделяет список сайтов на рабочие пакеты. Каждый пакет сайтов выполняется в отдельном потоке. По умолчанию платформа создает пять пакетов и пять потоков для выполнения этих пяти пакетов. Дополнительные сведения о параметрах потоков заданий таймера см. в разделе Потоки .

Когда поток обрабатывает пакет, событие TimerJobRun активируется платформой таймеров и предоставляет все необходимые сведения для выполнения задания таймера. Задания таймера выполняются как события, поэтому код должен подключить обработчик событий к событию TimerJobRun :

public SimpleJob() : base("SimpleJob")
{
    TimerJobRun += SimpleJob_TimerJobRun;
}

void SimpleJob_TimerJobRun(object sender, TimerJobRunEventArgs e)
{
    // your timer job logic goes here
}

Альтернативный подход — использование встроенного делегата, как показано ниже.

public SimpleJob() : base("SimpleJob")
{
    // Inline delegate
    TimerJobRun += delegate(object sender, TimerJobRunEventArgs e)
    {
        // your timer job logic goes here
    };
}

При возникновении события TimerJobRun вы получаете объект TimerJobRunEventArgs , который предоставляет необходимые сведения для записи логики задания таймера. В этом классе доступны следующие атрибуты и методы:

Структура класса TimerJobRunEventArgs

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

  • Свойство URL: возвращает или задает URL-адрес сайта для задания таймера, с которым нужно работать. Это может быть корневой сайт семейства веб-сайтов, но он также может быть дочерним сайтом в случае, если расширение сайта было выполнено.
  • Свойство ConfigurationData : возвращает или задает дополнительные данные конфигурации задания таймера (необязательно). Эти данные конфигурации передаются как часть объекта TimerJobRunEventArgs .
  • Свойство WebClientContext . Возвращает или задает объект ClientContext для текущего URL-адреса. Это свойство является объектом ClientContext для сайта, определенного в свойстве Url . Обычно это объект ClientContext , который используется в коде задания таймера.
  • Свойство SiteClientContext . Возвращает или задает объект ClientContext для корневого сайта семейства веб-сайтов. Это свойство предоставляет доступ к корневому сайту, если задание таймера требует доступа к нему. Например, задание таймера может добавить макет страницы в коллекцию страниц master с помощью свойства SiteClientContext.
  • Свойство TenantClientContext : возвращает или задает объект ClientContext для работы с API клиента. Это свойство предоставляет объект ClientContext , созданный с помощью URL-адреса сайта администрирования клиента. Чтобы использовать API клиента в обработчике событий TimerJobRun задания таймера , создайте объект Tenant с помощью этого свойства TenantClientContext .

Все объекты ClientContext используют сведения о проверке подлинности, описанные в разделе Проверка подлинности . Если вы выбрали учетные данные пользователя, убедитесь, что используемая учетная запись имеет необходимые разрешения для работы с указанными сайтами. При использовании только приложения лучше задать разрешения на уровне клиента для субъекта только приложения.

Управление состоянием

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

Управление состояниями сохраняет и извлекает набор стандартных и пользовательских свойств в виде сериализованной строки JSON в контейнере веб-свойств обработанного сайта (имя = имя задания таймера + "_Properties"). Ниже приведены свойства объекта TimerJobRunEventArgs по умолчанию:

  • Свойство PreviousRun . Возвращает или задает дату и время предыдущего выполнения.
  • Свойство PreviousRunSuccessful . Возвращает или задает значение, указывающее, был ли предыдущий запуск успешным. Обратите внимание, что автор задания таймера отвечает за пометку задания, выполняемого как успешного, задав свойство CurrentRunSuccessful в рамках реализации задания таймера.
  • Свойство PreviousRunVersion . Возвращает или задает версию задания таймера предыдущего выполнения.

Рядом с этими стандартными свойствами можно также указать собственные свойства, добавив пары ключевое слово–значение в коллекцию Properties объекта TimerJobRunEventArgs. Чтобы упростить эту задачу, существует три метода, которые помогут вам:

  • SetProperty добавляет или обновляет свойство.
  • GetProperty возвращает значение свойства.
  • DeleteProperty удаляет свойство из коллекции свойств.

В следующем коде показано, как можно использовать управление состоянием:

void SiteGovernanceJob_TimerJobRun(object o, TimerJobRunEventArgs e)
{
    try
    {
        string library = "";

        // Get the number of admins
        var admins = e.WebClientContext.Web.GetAdministrators();

        Log.Info("SiteGovernanceJob", "ThreadID = {2} | Site {0} has {1} administrators.", e.Url, admins.Count, Thread.CurrentThread.ManagedThreadId);

        // grab reference to list
        library = "SiteAssets";
        List list = e.WebClientContext.Web.GetListByUrl(library);

        if (!e.GetProperty("ScriptFileVersion").Equals("1.0", StringComparison.InvariantCultureIgnoreCase))
        {
            if (list == null)
            {
                // grab reference to list
                library = "Style%20Library";
                list = e.WebClientContext.Web.GetListByUrl(library);
            }

            if (list != null)
            {
                // upload js file to list
                list.RootFolder.UploadFile("sitegovernance.js", "sitegovernance.js", true);

                e.SetProperty("ScriptFileVersion", "1.0");
            }
        }

        if (admins.Count < 2)
        {
            // Oops, we need at least 2 site collection administrators
            e.WebClientContext.Site.AddJsLink(SiteGovernanceJobKey, BuildJavaScriptUrl(e.Url, library));
            Console.WriteLine("Site {0} marked as incompliant!", e.Url);
            e.SetProperty("SiteCompliant", "false");
        }
        else
        {
            // We're all good...let's remove the notification
            e.WebClientContext.Site.DeleteJsLink(SiteGovernanceJobKey);
            Console.WriteLine("Site {0} is compliant", e.Url);
            e.SetProperty("SiteCompliant", "true");
        }

        e.CurrentRunSuccessful = true;
        e.DeleteProperty("LastError");
    }
    catch(Exception ex)
    {
        e.CurrentRunSuccessful = false;
        e.SetProperty("LastError", ex.Message);
    }
}

Состояние хранится в виде одного сериализованного свойства JSON, что означает, что оно также может использоваться другими настройками. Например, если задание таймера записало запись состояния SiteCompliant=false, подпрограмма JavaScript может предложить пользователю действовать, так как задание таймера определило, что сайт не соответствует требованиям.

Threading

Платформа заданий таймера по умолчанию использует потоки для параллелизации работы. Потоки используются как для расширения дочернего сайта (по запросу), так и для выполнения логики фактического задания таймера (событие TimerJobRun ) для каждого сайта. Для управления реализацией потоков можно использовать следующие свойства:

  • Свойство UseThreading . Возвращает или задает значение, указывающее, используется ли поток. Значение по умолчанию: true. Установите значение false, чтобы выполнить все действия с помощью потока приложения main.
  • Свойство MaximumThreads . Возвращает или задает количество потоков, используемых для этого задания таймера. Допустимые значения: от 2 до 100. Значение по умолчанию равно 5. Наличие большого количества потоков не обязательно происходит быстрее, чем несколько потоков. Оптимальное число должно быть получено путем тестирования с использованием различных счетчиков потоков. По умолчанию 5 потоков значительно повышают производительность в большинстве сценариев.

Регулирование

Так как задание таймера использует потоки, а операции задания таймера обычно ресурсоемки, выполнение задания таймера можно регулировать. Чтобы правильно справиться с регулированием, платформа заданий таймера и вся платформа PnP Core используют метод ExecuteQueryRetry вместо метода ExecuteQuery по умолчанию.

Примечание.

Важно использовать ExecuteQueryRetry в коде реализации задания таймера.

Проблемы с параллелизмом — обработка всех дочерних сайтов семейства веб-сайтов в одном потоке

Задания таймера могут иметь проблемы с параллелизмом при использовании нескольких потоков для обработки дочерних сайтов.

Рассмотрим следующий пример: поток A обрабатывает первый набор дочерних сайтов из семейства веб-сайтов 1, а поток B — остальные дочерние сайты из семейства веб-сайтов 1. Если задание таймера обрабатывает дочерний сайт и корневой сайт (с помощью объекта SiteClientContext ), может возникнуть проблема с параллелизмом, так как поток A и поток B обрабатывают корневой сайт.

Чтобы избежать проблемы с параллелизмом (без запуска заданий таймера в одном потоке), используйте метод GetAllSubSites в задании таймера.

В следующем коде показано, как использовать метод GetAllSubSites в задании таймера:

public class SiteCollectionScopedJob: TimerJob
{
    public SiteCollectionScopedJob() : base("SiteCollectionScopedJob")
    {
        // ExpandSites *must* be false as we'll deal with that at TimerJobEvent level
        ExpandSubSites = false;
        TimerJobRun += SiteCollectionScopedJob_TimerJobRun;
    }

    void SiteCollectionScopedJob_TimerJobRun(object sender, TimerJobRunEventArgs e)
    {
        // Get all the subsites in the site we're processing
        IEnumerable<string> expandedSites = GetAllSubSites(e.SiteClientContext.Site);

        // Manually iterate over the content
        foreach (string site in expandedSites)
        {
            // Clone the existing ClientContext for the sub web
            using (ClientContext ccWeb = e.SiteClientContext.Clone(site))
            {
                // Here's the timer job logic, but now a single site collection is handled in a single thread which
                // allows for further optimization or prevents race conditions
                ccWeb.Load(ccWeb.Web, s => s.Title);
                ccWeb.ExecuteQueryRetry();
                Console.WriteLine("Here: {0} - {1}", site, ccWeb.Web.Title);
            }
        }
    }
}

Ведение журнала

Платформа заданий таймера использует компоненты ведения журнала PnP Core, так как она является частью библиотеки PnP Core. Чтобы активировать встроенное ведение журнала PnP Core, настройте его с помощью соответствующего файла конфигурации (app.config или web.config). В следующем примере показан необходимый синтаксис:

  <system.diagnostics>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <add name="DebugListenter" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log" />
        <!--<add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener" />-->
      </listeners>
    </trace>
  </system.diagnostics>

Используя приведенный выше файл конфигурации, платформа задания таймера использует System.Diagnostics.TextWriterTraceListener для записи журналов в файл с именем trace.log в той же папке, что и задание таймера .exe. Доступны другие прослушиватели трассировки, например:

Настоятельно рекомендуется использовать тот же подход к ведению журнала для пользовательского кода задания таймера, что и для платформы заданий таймера. В коде задания таймера можно использовать класс PnP Core Log :

void SiteGovernanceJob_TimerJobRun(object o, TimerJobRunEventArgs e)
{
    try
    {
        string library = "";

        // Get the number of admins
        var admins = e.WebClientContext.Web.GetAdministrators();

        Log.Info("SiteGovernanceJob", "ThreadID = {2} | Site {0} has {1} administrators.", e.Url, admins.Count, Thread.CurrentThread.ManagedThreadId);

        // Additional timer job logic...

        e.CurrentRunSuccessful = true;
        e.DeleteProperty("LastError");
    }
    catch(Exception ex)
    {
        Log.Error("SiteGovernanceJob", "Error while processing site {0}. Error = {1}", e.Url, ex.Message);
        e.CurrentRunSuccessful = false;
        e.SetProperty("LastError", ex.Message);
    }
}

См. также