WCF и длительные вычисления
От: d8m1k Россия  
Дата: 16.09.13 20:30
Оценка: 1 (1) :)
Здравствуйте! Есть проблема, которую не могу решить.

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

Создаю фоновые потоки так, по-современному, в задачах
for (int i = 1; i<=Q; ++i) {
  Task.Factory.StartNew(() => {
    doWork(n);
  });
}

или так, по старенькому, прямо в виде потоков
for (int i = 1; i<=Q; ++i) {
  new Thread(() => {
    doWork(n);
  }).Start();
}


Так вот оказывается функция опроса текущего состояния в WCF норовит дождаться выполнения этих потоков, если вычислений производится много. В случае с WinForm приложением всё отлично, фоновых процессов создаю сколько угодно, однако графический интерфейс не замерзает, кнопка "получить состояние" доступна. У UI потока хороший приоритет. В случае с WCF получается как будто поток из которого происходит опрос состояния имеет такой же приоритет как и фоновые потоки, и в случае запуска фоновой работы через задачи опрос состояния начинает хоть как-то срабатывать только когда фоновых задач становиться не больше чем ядер у процессора.

И тут не в блокировках дело. Так как например функция
public int GetNum() {
  return 42;
}

тоже тупит в WCF в случае большой нагрузки.

Сервис создаю с атрибутами:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext=true)]


пробовал перебирать флаги TaskCreationOptions в 1-ом случае и Thread.CurrentThread.Priority во втором, эффекта не заметил. Тут что то требуется концептуальное.

Тестовое приложение написал. Там цифры ежесекундно считываются с WCF. И ежесекундно считывается с WCF картинка с графиком выполнения процессов. После нажатия кнопки "Пуск" запускается указанное количество процессов, в каждом из которых эмулируется нагрузка + рисуются вертикальные палки на графике в строчках соответствующей заданиям. Видно что интерфейс замораживается, даже перестают считываться цифры, пока фоновая работа не выполнится. В случае данного одноядерного хостинга, замораживается полностью.

Может кто намекнёт, где покопать, чтоб можно было реализовать фоновую обработку в WCF адекватно?
wcf многопоточность
Re: WCF и длительные вычисления
От: Sharov Россия  
Дата: 17.09.13 06:37
Оценка:
Здравствуйте, d8m1k, Вы писали:

Из описание не совсем понятно в чем затык, но попробуйте использовать
асинхронные вызовы.
Кодом людям нужно помогать!
Re[2]: WCF и длительные вычисления
От: d8m1k Россия  
Дата: 17.09.13 08:22
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Из описание не совсем понятно в чем затык


Есть основной поток, вызывающий фоновые. В случае WinForms это GUI поток. Так вот в WinForms фоновые потоки совсем не затыкают GUI-поток. Я пробовал, хоть 50 их запусти, всё равно интерфейс виндовс-формы остаётся отзывчивым. И задания запущенные непосредственно в GUI потоке выполняются в 1-ую очередь, а уж потом фоновые. В случае с WCF картина грустная, фоновые потоки могут полностью заблокировать вызывающий в случае если фоновых достаточно много (больше чем ядер процессора) и и они достаточно активные. Пробовал сажать Thread.Sleep(0) в фоновые потоки — бесполезно. Я не знаю как сделать, чтобы виндовс на поток сервиса WCF уделял такое же внимание как активному окну десктопного приложения.
Re: WCF и длительные вычисления
От: d8m1k Россия  
Дата: 17.09.13 08:28
Оценка: :)
Частично ситуацию удалось выправить в приемлемую сторону поменяв
ConcurrencyMode на ConcurrencyMode.Single
и выставив приоритеты
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
Тестовая программка уже повеселей работает, иногда реагирует до завершения фоновых потоков. Но это не выход.
Re: WCF и длительные вычисления
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 17.09.13 08:33
Оценка: 3 (1)
Здравствуйте, d8m1k, Вы писали:

D>Здравствуйте! Есть проблема, которую не могу решить.


D>Допустим сервер выполняет некие длительные вычисления. Выполняет их в фоновых потоках. В WCF я предусмотрел функции запуска этих длительных процессов, а так же функции опроса текущего состояния о проделанной работе.


D>Создаю фоновые потоки так, по-современному, в задачах

D>
D>for (int i = 1; i<=Q; ++i) {
D>  Task.Factory.StartNew(() => {
D>    doWork(n);
D>  });
D>}
D>



С этим кодом что-то не то. Нужно не создавать кучу рабочих потоков для выполнения всех задач, а сделать набор, например, активных объектов, которые будут выполнять свою работу асинхронно/параллельно.

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

class MyServiceImpl
{
    public Task<int> GetAnswerToTheUltimateQuestionOfLife()
    {
        return TAsk.Factory.StartNew() => {Thread.Sleep(10000); return 42;}
    }
}


Теперь у вас будет класс с бизнес-логикой и WCF фасад для работы с этим делом. Тут тоже есть несколько подходов. Во-первых, вместо опроса текущего состояния (pull модель) можно воспользоваться механизмом методов обратного вызова и реализовать push-модуль (с помощью callback интерфейсов) или же сразу посмотреть на использование асинхронных операций в WCF, например здесь — Synchronous and Asynchronous Operations.

D>И тут не в блокировках дело. Так как например функция

D>
D>public int GetNum() {
D>  return 42;
D>}
D>

D>тоже тупит в WCF в случае большой нагрузки.

А в этом случае нужно курить WCF Throttling, поскольку по умолчанию нельзя совершать более 10 (кажется) одновременных вызовов сервиса, все остальные запросы будут сериализованы и будут ждать своей очереди (что логично, ведь как можно заставить входной горшочек больше не варить? )

D>Сервис создаю с атрибутами:

D>
D>[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext=true)]
D>


НИКОГДА, ПОВТОРЯЮ, НИКОГДА НЕ ВКЛЮЧАЙТЕ КОНТЕКСТ СИНХРОНИЗАЦИИ!!!! Это страшное зло, поскольку при хостинге вашего сервиса в Винформах (не дай Бог, конечно) или в любом другом месте с непустым контекстом синхронизации, ВСЕ ВАШИ ЗАПРОСЫ БУДУТ ВЫПОЛНЯТЬСЯ ПОСЛЕДОВАТЕЛЬНО!! Фактически, в случае с винформами, все серверные операции будут выполняться в потоке пользовательского интерфейса!

D>пробовал перебирать флаги TaskCreationOptions в 1-ом случае и Thread.CurrentThread.Priority во втором, эффекта не заметил. Тут что то требуется концептуальное.


Это не к чему, проблема не в этом

D>Тестовое приложение написал.


Тут бы не приложение, а исходный код этого приложения! Но, я думаю, что общая проблема понятна:
1) У сервиса есть лимит на одновременное количество исполняемых запросов
2) Контекст синхронизации для сервисов — страшное зло
3) Асинхронность должна быть сделана вменяемым образом и она уже встроена в WCF, поэтому лучше использовать проверенные подходы, а не изобретать велосипед!
D>Может кто намекнёт, где покопать, чтоб можно было реализовать фоновую обработку в WCF адекватно?
Re[2]: WCF и длительные вычисления
От: Sharov Россия  
Дата: 17.09.13 08:41
Оценка:
Здравствуйте, d8m1k, Вы писали:

D>Частично ситуацию удалось выправить в приемлемую сторону поменяв

D>ConcurrencyMode на ConcurrencyMode.Single

Перенесли головную боль с сервера на клиента.

D>и выставив приоритеты

D>Thread.CurrentThread.Priority = ThreadPriority.Highest;
Может приоритеты фоновым как раз пониже?
Кодом людям нужно помогать!
Re: исходный код тестового приложения
От: d8m1k Россия  
Дата: 17.09.13 11:50
Оценка:
Клиет находится тут, сервер вот он:
[ServiceContract]
public interface IThread {
    [OperationContract]
    void Start(int nTasks, int nActions);

    [OperationContract]
    byte[] Status();
        
    [OperationContract]
    int StatusSimple();
}


[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext=false)]
public class WebThread : IThread {
    public WebThread() {
        //Thread.CurrentThread.Priority = ThreadPriority.Highest;
        //Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
        graphics = Graphics.FromImage(bitmap);
    }

    public void Start(int nTasks, int nActions) {
        bitmap = new Bitmap(Width, (nTasks+1)*10);
        graphics= Graphics.FromImage(bitmap);
        x=0;

        for (int i = 1; i<= nTasks; ++i) {
            var n = i;
            Task.Factory.StartNew(() => {
                doWork(n, nActions);
            });
            //new Thread(() => {
            //  doWork(n,qActions);
            //}).Start();
        }
    }

    public byte[] Status() {
        using (var ms = new MemoryStream()) {
            lock(bitmap)
                bitmap.Save(ms, ImageFormat.Png);
            return ms.ToArray();
        }
    }
    public int StatusSimple() {
        return n++;
    }
    private int n=0;//номер обращения

    private void doWork(int n, int qActions) {
        Thread.CurrentThread.Priority = ThreadPriority.Lowest;//наполовину помогло
        for (int j =0; j<qActions; j++) {
            for (var k=0; k<1000000; ++k) 
                v1=1/(j+rnd.NextDouble());
            log(n);
        }
        logEnd(n);
    }

    private void log(int n) {
        lock (bitmap) {
            graphics.DrawLine(new Pen(Color.Black), x, n*10, x, (n+1)*10-1);
            x++;
        }
    }
    private void logEnd(int n){
        lock (bitmap) {
            graphics.FillRectangle(new SolidBrush(Color.LightBlue), x, n*10, Width-x, 10);
            x++;
        }
    }

    private int Width = 1700;
    private int x = 0;
    private Bitmap bitmap = new Bitmap(1, 1);
    private Graphics graphics;
    private readonly Random rnd = new Random();
    private double v1;
}


Веб-клиент по кнопке запускает Start() и ежесекундно дёргает Status() и StatusSimple().
Status() и StatusSimple() задуманы как быстрые функции опроса текущего состояния результатов фоновой работы, и мне кажется, никакая асинхронность в них не нужна, т.к. задача в том что бы эти функции быстро отрабатывали сразу, а не откладывали дополнительно получения статусов на потом.
По своим экспериментам вижу, что фоновые потоки не дают отрабатывать Status() и StatusSimple() довольно долго. Выставление приоритетов помогает частично. Всё равно основной поток намного хуже работает GUI потока активного окна в случае десктопа.
Re[2]: WCF и длительные вычисления
От: d8m1k Россия  
Дата: 17.09.13 12:36
Оценка:
ST>
ST>class MyServiceImpl
ST>{
ST>    public Task<int> GetAnswerToTheUltimateQuestionOfLife()
ST>    {
ST>        return TAsk.Factory.StartNew() => {Thread.Sleep(10000); return 42;}
ST>    }
ST>}
ST>


ST>Теперь у вас будет класс с бизнес-логикой и WCF фасад для работы с этим делом. Тут тоже есть несколько подходов. Во-первых, вместо опроса текущего состояния (pull модель) можно воспользоваться механизмом методов обратного вызова и реализовать push-модуль (с помощью callback интерфейсов) или же сразу посмотреть на использование асинхронных операций в WCF, например здесь — Synchronous and Asynchronous Operations.


А не знаете сервис WCF в виде callback возможно использовать в PHP или даже в JavaScript? Я только нашёл тут и тут вроде как нельзя.
Re[3]: WCF и длительные вычисления
От: d8m1k Россия  
Дата: 17.09.13 12:39
Оценка:
D>>Частично ситуацию удалось выправить в приемлемую сторону поменяв
D>>ConcurrencyMode на ConcurrencyMode.Single

S>Перенесли головную боль с сервера на клиента.


Забота клиента только получать данные о текущем состоянии процесса как можно быстрее.

D>>и выставив приоритеты

D>>Thread.CurrentThread.Priority = ThreadPriority.Highest;
S>Может приоритеты фоновым как раз пониже?

А вот это помогло, как раз так и сделал в примере http://www.rsdn.ru/forum/dotnet/5298881.1
Автор: d8m1k
Дата: 17.09.13
Re[3]: WCF и длительные вычисления
От: Sharov Россия  
Дата: 17.09.13 12:50
Оценка:
Здравствуйте, d8m1k, Вы писали:

D>А не знаете сервис WCF в виде callback возможно использовать в PHP или даже в JavaScript? Я только нашёл тут и тут вроде как нельзя.


http://dotnetbyexample.blogspot.ru/2008/02/calling-wcf-service-from-javascript.html
http://social.msdn.microsoft.com/Forums/vstudio/en-US/f0e6d5f2-a15f-4cbd-83de-87e6fb67428f/wcf-server-callbacks-in-javascript

Я сейчас, собственно, подобный сервис и разрабатываю, но у меня
взаимодействие идет через WebAPI (REST). С callback'aми работать пока не
доводилось.
Кодом людям нужно помогать!
Re[4]: WCF и длительные вычисления
От: Sharov Россия  
Дата: 17.09.13 12:54
Оценка:
Здравствуйте, d8m1k, Вы писали:

D>>>Частично ситуацию удалось выправить в приемлемую сторону поменяв

D>>>ConcurrencyMode на ConcurrencyMode.Single

S>>Перенесли головную боль с сервера на клиента.


D>Забота клиента только получать данные о текущем состоянии процесса как можно быстрее.


Эти данные должен кто-то возвращать. "Исполнитель" работает в единственно экземпляре.
Это нормально, когда кол-во клиентов небольшое, в противном случае будет bottleneck.
Кодом людям нужно помогать!
Re[2]: исходный код тестового приложения
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 17.09.13 14:50
Оценка:
Здравствуйте, d8m1k, Вы писали:

В этом случае меня очень сильно смущает ConcurrencyMode.Single

Single

The service instance is single-threaded and does not accept reentrant calls. If the InstanceContextMode property is Single, and additional messages arrive while the instance services a call, these messages must wait until the service is available or until the messages time out.


D>Клиет находится тут, сервер вот он:

D>
D>[ServiceContract]
D>public interface IThread {
D>    [OperationContract]
D>    void Start(int nTasks, int nActions);

D>    [OperationContract]
D>    byte[] Status();
        
D>    [OperationContract]
D>    int StatusSimple();
D>}
D>


Потом, я очень надеюсь, что IThread — это всего лишь пример, а не реальный код, поскольку текущий пример никак не вяжется с моим видением дизайна вообще и SOA, в частности. Так, меня смущают имена (они ничего не говорят о том, что они делают), меня смущают типы (почему Status возвращает массив байт?, почему StatusSimple — это int)? Если это все в качестве тестов, то ОК, если из этого "прототипа" что-то переедет в продакшн, то это не хорошо.

К тому же, сюда очень классно ложится идея реактивного программирования
Автор: Тепляков Сергей Владимирович
Дата: 06.02.11
вообще, и упомянутых callback интерфейсов в частности.

В результате, я бы дизайнил сервис таким образом: есть метод сервиса:

[c#]
[ServiceContract(CallbackContract = typeof(ICustomServiceCallback)]
public interface ICustomService
{
// Метод должен называться так, чтобы было понятно, что он делает.
// вообще говоря, taskCount и actionsCount — это деталь реализации сервиса и должна
// управляться именно им. Ведь именно при деплоя сервиса будет известно, какое
// количество одновременных задач является максимально эффективным с точки зрения эффективности
// Метод возвращает некий session id, который мы потом будет передавать в callback-и,
// чтобы клиент понял, для какого запроса приходят данные
string StartProcessing();
}

public interface ICustomServiceCallback
{
// Через этот метод мы будем уведомлять клиента о том, что операция исполняется
// и мы уже получили некоторые результаты
void Progress(string correlationId, CustomType[] processedData);
}
[c#]

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

Я бы вначале поигрался бы с параллелизмом, чтобы понять, как в рамках одного процесса получить максимальную пропускную способность от системы, а потом бы уже завернул бы это все дело в фасадный сервис, для предоставлени удаленных услуг.
Re[3]: исходный код тестового приложения
От: d8m1k Россия  
Дата: 17.09.13 19:56
Оценка:
ST>В этом случае меня очень сильно смущает ConcurrencyMode.Single

ST>The service instance is single-threaded and does not accept reentrant calls. If the InstanceContextMode property is Single, and additional messages arrive while the instance services a call, these messages must wait until the service is available or until the messages time out.


С этим я разобрался. InstanceContextMode однозначно Single. Иначе что же я буду опрашивать если при каждом опросе будет создаваться новый экземпляр опрашиваемого класса.
ConcurrencyMode в случае Single в тестовом приложении запросы накапливаются пока процессор занят, потом быстренько друг за дружкой выполняются. В случает Multiple тоже ждут процессора, потом выполняются параллельно.
Смущает, что они сразу не выполняются.

ST>Потом, я очень надеюсь, что IThread — это всего лишь пример, а не реальный код, поскольку текущий пример никак не вяжется с моим видением дизайна вообще и SOA, в частности. Так, меня смущают имена (они ничего не говорят о том, что они делают), меня смущают типы (почему Status возвращает массив байт?, почему StatusSimple — это int)? Если это все в качестве тестов, то ОК, если из этого "прототипа" что-то переедет в продакшн, то это не хорошо.


Да, это тестовое приложение. Status я назвал в смысле что возвращаю состояния. В продакшн готовлю другое.

ST>К тому же, сюда очень классно ложится идея реактивного программирования
Автор: Тепляков Сергей Владимирович
Дата: 06.02.11
вообще, и упомянутых callback интерфейсов в частности.


Rx, конечно, хорошо. Читал это, вещь классная. Но как выйти с этим за пределы .NET я пока не представляю

ST>В результате, я бы дизайнил сервис таким образом: есть метод сервиса:


ST>[c#]

ST>[ServiceContract(CallbackContract = typeof(ICustomServiceCallback)]
ST>public interface ICustomService
ST>{
ST> // Метод должен называться так, чтобы было понятно, что он делает.
ST> // вообще говоря, taskCount и actionsCount — это деталь реализации сервиса и должна
ST> // управляться именно им. Ведь именно при деплоя сервиса будет известно, какое
ST> // количество одновременных задач является максимально эффективным с точки зрения эффективности
ST> // Метод возвращает некий session id, который мы потом будет передавать в callback-и,
ST> // чтобы клиент понял, для какого запроса приходят данные
ST> string StartProcessing();
ST>}

ST>public interface ICustomServiceCallback

ST>{
ST> // Через этот метод мы будем уведомлять клиента о том, что операция исполняется
ST> // и мы уже получили некоторые результаты
ST> void Progress(string correlationId, CustomType[] processedData);
ST>}
ST>[c#]

Спасибо за подсказку!
Осталось мне в принципе получить представление о реализации push-модуля в веб. Подписываться на события изменения состояний сервера, конечно, правильнее чем постоянные его опросы. Но хочется что бы это было реализуемо без требований открытия дополнительных портов или поддержки каких то протоколов, менее распространённых чем HTTP.

ST>В общем, мне кажется, что вы смешиваете проблему параллелизма с WCF-ом, при этом каждая из этих проблем довольно сложная. WCF сервисы не должны выполнять такую бизнес-логику, как многопоточный процессинг изображений и уж точно клиент не должне задвать, во сколько рук мы будем получать результаты. Ведь параллелизм определяется количеством физических процессоров (или ядер) на сервисе и неправильное количество рабочих потоков просто убъет всю производительность!


Может с изображением погорячился, но измерил процессинг изображения 2000x2000 выполняется 200мс. У меня изображение в случае 5 потоков в 20 раз меньше, думаю достаточно быстро. Готов пример переделать на возврат коллекций данных, что быстрее. Не мне кажется загвозтка не в этом.
А о количестве потоков заботится Task.Factory

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


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