Наследование от SynchronizationContext
От: Аноним  
Дата: 24.06.10 12:58
Оценка:
Добрый день ,

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

_asyncOperation.Post(taskProgressCallback, new ProgressChangedEventArgs(0, i.ToString()));

в родительском потоке есть обработчик:

static void TaskProgressRoutine(object sender, ProgressChangedEventArgs e)
{
Console.Write(string.Format(" {0}", e.UserState));
}

каждый новый вызов TaskProgressRoutine выводит на консоль число и числа должны идти строго в возрастающем порядке.
Иногда порядок вывода чисел нарушается. В МСДН, в описании на метод AsyncOperation.Post:

Note:
Console applications do not synchronize the execution of Post calls. This can cause ProgressChanged events to be raised out of order. If you wish to have serialized execution of Post calls, implement and install a System.Threading.SynchronizationContext class.

Прошу подсказать ссылки на примеры наследования от SynchronizationContext и использования его с AsyncOperation.
Re: Наследование от SynchronizationContext
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 24.06.10 13:25
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Прошу подсказать ссылки на примеры наследования от SynchronizationContext и использования его с AsyncOperation.


Так в самом фреймворке есть наследники. Например WinFormsSyncContext.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
AVK Blog
Re[2]: Наследование от SynchronizationContext
От: Аноним  
Дата: 24.06.10 13:34
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Так в самом фреймворке есть наследники. Например WinFormsSyncContext.


В консольной программе подойдет для использования?
Re[3]: Наследование от SynchronizationContext
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 24.06.10 13:35
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>В консольной программе подойдет для использования?


Если цикл выборки сообщений запустишь в вызывающем потоке — подойдет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
AVK Blog
Re[4]: Наследование от SynchronizationContext
От: Аноним  
Дата: 24.06.10 13:46
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Если цикл выборки сообщений запустишь в вызывающем потоке — подойдет.


Прошу Вас пояснить как это можно сделать?
Re[5]: Наследование от SynchronizationContext
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 24.06.10 13:48
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Прошу Вас пояснить как это можно сделать?


Application.Run().
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
AVK Blog
Re[6]: Наследование от SynchronizationContext
От: Аноним  
Дата: 24.06.10 13:58
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Application.Run().


Посмотрел описание на класс System.Windows.Forms.Application. Он предназначен для приложений WinForms.
Непонятно как можно использовать данный метод в консольной программе ?
Re[7]: Наследование от SynchronizationContext
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 24.06.10 14:06
Оценка:
Здравствуйте, <Аноним>, Вы писали:

А>Непонятно как можно использовать данный метод в консольной программе ?


Что то мешает?
... << RSDN@Home 1.2.0 alpha 4 rev. 1476 on Windows 7 6.1.7600.0>>
AVK Blog
Re: Наследование от SynchronizationContext
От: TK Лес кывт.рф
Дата: 24.06.10 17:20
Оценка: +1
Здравствуйте, Аноним, Вы писали:

А>В моей консольной программе, дочерний поток сообщает о прогрессе выполнения долгой операции

А>через вызов:

А>_asyncOperation.Post(taskProgressCallback, new ProgressChangedEventArgs(0, i.ToString()));


А>Note:

А>Console applications do not synchronize the execution of Post calls. This can cause ProgressChanged events to be raised out of order. If you wish to have serialized execution of Post calls, implement and install a System.Threading.SynchronizationContext class.

А>Прошу подсказать ссылки на примеры наследования от SynchronizationContext и использования его с AsyncOperation.


Для начала определитесь как "консольный" поток будет получать уведомления посланные через Post (аналогично и для Send) — для получения любого сообщения поток должен его "ждать". Сообщение само доставиться и обработаться не сможет...

Тут надо сделать специальную очередь (и переодически ее проверять в консольном потоке) либо, использовать контекст из Windows.Forms
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: Наследование от SynchronizationContext
От: Sinix  
Дата: 25.06.10 00:46
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Посмотрел описание на класс System.Windows.Forms.Application. Он предназначен для приложений WinForms.

А>Непонятно как можно использовать данный метод в консольной программе ?

Пожалуйста, точно по ТЗ
      SynchronizationContext context;

      using (AutoResetEvent wait = new AutoResetEvent(false)) // TODO: using вместе с содержимым - в отдельный метод.
      {
        ThreadStart threadStart = () =>
        {
          EventHandler idle = null;
          idle = (sender, e) =>
          {
            Application.Idle -= idle;
            context = SynchronizationContext.Current;
            wait.Set();
          };

          Application.Idle += idle;
          Application.Run();
        };

        Thread thread = new Thread(threadStart);
        thread.IsBackground = true;
        thread.SetApartmentState(ApartmentState.STA);

        thread.Start();

        wait.WaitOne();
      }

      // Your code goes here.


На самом деле заводится ConcurrentQueue<Action> и фоновый поток, выгребающий делегаты из очереди и выполняющий их.
Потом (если требуется) пишется обёртка, наследуемая от SynchronizationContext.
Re[2]: Наследование от SynchronizationContext
От: Аноним  
Дата: 25.06.10 05:55
Оценка:
Здравствуйте, TK, Вы писали:

TK>Для начала определитесь как "консольный" поток будет получать уведомления посланные через Post (аналогично и для Send) — для получения любого сообщения поток должен его "ждать". Сообщение само доставиться и обработаться не сможет...


С этим проблем нет. Родительский поток ждет, и обработчик:
static void TaskProgressRoutine(object sender, ProgressChangedEventArgs e)
{
Console.Write(string.Format(" {0}", e.UserState));
}

вызывается в родительском потоке в ответ на каждый вызов _asyncOperation.Post(...)
просто обратил внимание что, порядок вызова обработчика не соответсвует порядку вызова _asyncOperation.Post(...)
и в МСДН нашел "Note" по этому поводу (см. исходный пост).
Re[8]: Наследование от SynchronizationContext
От: Аноним  
Дата: 25.06.10 07:00
Оценка:
Добрый день Sinix,
Вы писали:

S>На самом деле заводится ConcurrentQueue<Action> и фоновый поток, выгребающий делегаты из очереди и выполняющий их.

S>Потом (если требуется) пишется обёртка, наследуемая от SynchronizationContext.

Прошу пояснения по коду из Вашего предыдущего поста.
По исходному условию, в моей консольной программе 2 потока.

Как я понял мне потребуется в родительском потоке провести инициализацию и создать ещё один дочерний поток (с кодом, который Вы привели).
После завершения работы приложения в главном потоке вызвать Application.Exit()
Правильно я понимаю ?
c# .net 3.5
Re[9]: Наследование от SynchronizationContext
От: Sinix  
Дата: 25.06.10 08:14
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Как я понял мне потребуется в родительском потоке провести инициализацию и создать ещё один дочерний поток (с кодом, который Вы привели).


Код, который я привёл, как раз запускает дочерний поток

А>После завершения работы приложения в главном потоке вызвать Application.Exit()


Необязательно — поток фоновый (background = true) и будет автоматом прибит по завершению основных потоков. Если для очистки совести хотите вызывать Application.Exit() — вызов надо делать из фонового потока.

P.S. Я бы остановился на своей очереди.
Re[10]: Наследование от SynchronizationContext
От: Аноним  
Дата: 25.06.10 11:53
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Код, который я привёл, как раз запускает дочерний поток

Действительно, я ступил
Просто мне ранее не преходилось вставлять обработку сообщений (с помощью Application.Run()) в консольное .Net приложение. Поэтому и возникает много вопросов.

S>Необязательно — поток фоновый (background = true) и будет автоматом прибит по завершению основных потоков. Если для очистки совести хотите вызывать Application.Exit() — вызов надо делать из фонового потока.

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


        private static readonly EventWaitHandle waitHandlerTaskCompleted = new EventWaitHandle(false, EventResetMode.ManualReset);

        static void Main(string[] args)
        {
            try
            {
                if (args.Length != COUNT_PROGRAM_PARAMETERS)
                    throw new AlwaysIntegerException(String.Format("Программа имеет только {0} параметр(ов) (файл данных)", COUNT_PROGRAM_PARAMETERS));
                List<AlwaysIntegerTask> alwaysIntTasks = ReadInputFile(args[0]).Select(Task => Task).ToList();
                foreach (AlwaysIntegerTask alwaysIntegerTask in alwaysIntTasks)
                {
                    alwaysIntegerTask.TaskCompleted += AlwaysIntegerTaskCompletedRoutine;
                    alwaysIntegerTask.TaskErrorCompleted += AlwaysIntegerTaskErrorRoutine;
                    alwaysIntegerTask.TaskProgress += AlwaysIntegerTaskProgressRoutine;
                    alwaysIntegerTask.ExecuteAsync(); 
                    waitHandlerTaskCompleted.WaitOne(); //здесь ожидаем завершения дочернего потока и в это время вызываются обработчики см. ниже....
                    waitHandlerTaskCompleted.Reset();
                    alwaysIntegerTask.JoinAlwaysIntegerTask();
                    //---
                    alwaysIntegerTask.TaskCompleted -= AlwaysIntegerTaskCompletedRoutine;
                    alwaysIntegerTask.TaskErrorCompleted -= AlwaysIntegerTaskErrorRoutine;
                    alwaysIntegerTask.TaskProgress -= AlwaysIntegerTaskProgressRoutine;
                }
                waitHandlerTaskCompleted.Close();
            }
            catch (Exception ex)
            {
                //вывод сообщения об ошибке;
            }
            //сообщение о завершении работы прграммы;
        }



Обработчики событий (вызываются в контексте главного потока) активируемых через _asyncOperation.Post() из дочернего потока.


       static void AlwaysIntegerTaskCompletedRoutine(object sender, TaskCompletedEventArgs e)
        {
            Trace.WriteLineIf(AlwaysIntegerAppSwitch.Enabled, string.Format("  {0}", e.FinalResult));
        }

        static void AlwaysIntegerTaskErrorRoutine(object sender, TaskCompletedEventArgs e)
        {
            Trace.WriteLineIf(AlwaysIntegerAppSwitch.Enabled, string.Format("Error: {0}", e.FinalResult));
        }

        static void AlwaysIntegerTaskProgressRoutine(object sender, ProgressChangedEventArgs e)
        {
            Trace.WriteIf(AlwaysIntegerAppSwitch.Enabled, string.Format(" {0}", e.UserState));
        }

Класс дочернего потока:
    public partial class AlwaysIntegerTask
    {
        private Thread threadForTask;
        // событие о прогрессе при построении
        public event EventHandler<ProgressChangedEventArgs> TaskProgress;
        // событие о завершении задачи
        public event EventHandler<TaskCompletedEventArgs> TaskCompleted;
        //делегаты для синхронизации вызова обработчиков событий в нужном потоке
        private readonly SendOrPostCallback taskProgressCallback;
        private readonly SendOrPostCallback taskCompletedCallback;
        //инициализируем объект класса AsyncOperation до создания потока
        private readonly AsyncOperation _asyncOperation;
        private readonly EventWaitHandle manualEventTerminateTask;

        public AlwaysIntegerTask(uint _CountTask, string _Polynome, EventWaitHandle eventWaitHandle)
            : this(eventWaitHandle)
        {
            //--- Прочая инициализация
        }

        private AlwaysIntegerTask(EventWaitHandle eventWaitHandle)
        {
            taskProgressCallback = ProgressThreadSafeProcess;
            taskCompletedCallback = CompletedThreadSafeProcess;
            _asyncOperation = AsyncOperationManager.CreateOperation(null);
            manualEventTerminateTask = eventWaitHandle;
        }

        // Функция асинхронного запуска расчета сдвига шаров
        public void ExecuteAsync()
        {
            threadForTask = new Thread(ExecuteAsyncTask);
            threadForTask.Name = String.Format("Task_{0}", CountTask);
            threadForTask.Start();
        }

        protected virtual void OnCompleted(TaskCompletedEventArgs e)
        {
            EventHandler<TaskCompletedEventArgs> localHandler = TaskCompleted;
            if (localHandler != null)
            {
                localHandler(this, e);
            }
        }

        protected virtual void OnProgress(ProgressChangedEventArgs e)
        {
            EventHandler<ProgressChangedEventArgs> localHandler = TaskProgress;
            if (localHandler != null)
            {
                localHandler(this, e);
            }
        }

        private void CompletedThreadSafeProcess(object e)
        {
            OnCompleted((TaskCompletedEventArgs)e);
        }

        private void ProgressThreadSafeProcess(object e)
        {
            OnProgress((ProgressChangedEventArgs)e);
        }

        public void JoinAlwaysIntegerTask()
        {
            threadForTask.Join();
        }

        private void ExecuteAsyncTask()
        {
            try
            {
              //Обработка и продолжительные вызовы
              _asyncOperation.Post(taskProgressCallback, new ProgressChangedEventArgs(0, i.ToString()));
              .....
              .....
              _asyncOperation.PostOperationCompleted(taskErrorCompletedCallback, new TaskCompletedEventArgs(ex.Message));
            }
            catch (Exception ex)
            {
                _asyncOperation.PostOperationCompleted(taskErrorCompletedCallback, new TaskCompletedEventArgs(ex.Message));
            }
            manualEventTerminateTask.Set();
        }

    public class TaskCompletedEventArgs : EventArgs
    {
        public string FinalResult { get; private set; }

        public TaskCompletedEventArgs(string result)
        {
            FinalResult = result;
        }
    }
c# .net 3.5
Re[11]: Наследование от SynchronizationContext
От: Sinix  
Дата: 25.06.10 13:42
Оценка: 3 (1)
Здравствуйте, Аноним, Вы писали:

А>Показываю исходный код.

Теперь я торможу. Зачем вы вызываете действия асинхронно, если они всё равно будут выполняться по одному?

foreach (AlwaysIntegerTask alwaysIntegerTask in alwaysIntTasks)
{
  // ...
  alwaysIntegerTask.ExecuteAsync(); 
  waitHandlerTaskCompleted.WaitOne(); // ?!!
  // Кстати, если использовать AutoResetEvent, waitHandlerTaskCompleted.Reset() не нужен.
  // ...
}


Тут проще оставить числомолотилку в основном потоке и в нём же использовать Tracer + ConsoleTraceListener (кстати, класс Console потокобезопасен, если вам нужна синхронизация только для вывода на консоль, synchronization context не нужен).

Если же вы собираетесь запускать несколько потоков одновременно (в исходном посте об этом — ни слова), можно переписать так:
      EventHandler idle = null;
      idle = (sender, e) =>
      {
        Application.Idle -= idle;
        SynchronizationContext context = SynchronizationContext.Current;
        
        // Тут запускаем все фоновые потоки
        // + ещё один, который по завершению фоновых вызовет Application.Exit().
        // после этого выходим из idle.
      };

      Application.Idle += idle;

      Application.Run();


Первый пример был написан для другого сценария: в главном потоке работает числомолотилка, в фоновом — вывод на консоль.
Re[12]: Наследование от SynchronizationContext
От: Аноним  
Дата: 29.06.10 06:47
Оценка:
Добрый день Sinix, Вы писали:

S>Теперь я торможу. Зачем вы вызываете действия асинхронно, если они всё равно будут выполняться по одному?

Дочерний поток будет наварачиваться, в нём будет запускаться ещё 2 дочерних потока.

S>
S>foreach (AlwaysIntegerTask alwaysIntegerTask in alwaysIntTasks)
S>{
S>  // ...
S>  alwaysIntegerTask.ExecuteAsync(); 
S>  waitHandlerTaskCompleted.WaitOne(); // ?!!
S>  //Кстати, если использовать AutoResetEvent, waitHandlerTaskCompleted.Reset() не нужен.
    //Согласен по поводу AutoResetEvent    
S>}
S>


S>Если же вы собираетесь запускать несколько потоков одновременно (в исходном посте об этом — ни слова), можно переписать так:


S>
S>      EventHandler idle = null;
S>      idle = (sender, e) =>
S>      {
S>        Application.Idle -= idle;
S>        SynchronizationContext context = SynchronizationContext.Current;
          //Прошу пояснить Вас как использовать переменную context ?
 
S>        //Тут запускаем все фоновые потоки
S>        //+ ещё один, который по завершению фоновых вызовет Application.Exit().
S>        //после этого выходим из idle.
S>      };
S>      Application.Idle += idle;
S>      Application.Run();
S>
Re[13]: Наследование от SynchronizationContext
От: Sinix  
Дата: 29.06.10 09:28
Оценка:
Здравствуйте, Аноним, Вы писали:

А> как использовать переменную context?


Передавать в методы, которые будут выполняться асинхронно
Сорри, но поскольку я через три часа уезжаю в отпуск, на предмет ответов придётся пытать кого-нить ещё
Re[12]: Наследование от SynchronizationContext
От: Аноним  
Дата: 30.06.10 13:35
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Если же вы собираетесь запускать несколько потоков одновременно (в исходном посте об этом — ни слова), можно переписать так:

S>
S>      EventHandler idle = null;
S>      idle = (sender, e) =>
S>      {
S>        Application.Idle -= idle;
S>        SynchronizationContext context = SynchronizationContext.Current;
        
S>        // Тут запускаем все фоновые потоки
S>        // + ещё один, который по завершению фоновых вызовет Application.Exit().
S>        // после этого выходим из idle.
S>      };

S>      Application.Idle += idle;

S>      Application.Run();
S>



Спасибо Вам, разобрался. Проблема, изложенная в исходном посте решена.
c# .net 3.5
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.