Какие есть методы вызова события в программе на VB.NET/С# с повышенной точностью? Нужно обрабатывать данные по точным заданным меткам времени и так что бы событие успевало обрабатываться быстро (скорее всего асинхронно).
4 Answers
Вам надо посмотреть в сторону технологий Complex Event Processing (CEP).
Так, от Microsoft есть StreamInsight, но есть и другие.
- 1,786
Нет таких методов, особенно асинхронно.
- 39
-
Можно узнать, в чем конкретно проблема вызова асинхронного метода по событию? Допустим вызов метода подписанного на событие вызывается исключительно синхронно (я не проверял, не знаю, возможно это так и есть), то если это так, то в чем проблема из этого синхронного метода подписанного на ивент запустить таску, которая бы асинхронно решала задачу? – Andrew Stop_RU_war_in_UA Feb 03 '19 at 18:04
-
Я проверял. Можете убедиться: http://amsawa.narod.ru/data/Test_uDelay.rar. Там асинхронный вызов задержки выдаёт показательные результаты, а с синхронным всё как и ожидалось. – Sawa Akisawa Feb 04 '19 at 06:35
то есть мы тупо используем таймер на форме и все будет прекрасно работать и мы точно сможем отследить например наступление времени 00:00: +100 миллисекунд?
С этих слов делаем вывод что допустимая ошибка является 100ms. И со слов:
Я просто знаю что есть таймер на форме и знаю что он тупит при долгой обработке данных в клиентском коде.
Делаем вывод что должен использоватся System.Windows.Forms.Timer.
Пишем тестовую программу:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
namespace TestWinFormsApp
{
public partial class Form1 : Form
{
Timer _tm = new Timer();
Stopwatch _st = new Stopwatch();
List<long> _log = new List<long>();
long _lastElapsed = 0;
public Form1()
{
InitializeComponent();
_tm.Tick += new EventHandler(tm_Tick);
_tm.Interval = 100;
_st.Start();
_tm.Start();
}
private void tm_Tick(object sender, EventArgs e)
{
_log.Add(_st.ElapsedMilliseconds - _lastElapsed - _tm.Interval);
_lastElapsed = _st.ElapsedMilliseconds;
if (_log.Count == 300)
{
string[] log = new[] { _log.Average().ToString() };
System.IO.File.WriteAllLines(@"D:\TestLog.txt", log);
MessageBox.Show("finish!");
}
}
}
}
У меня в лог записалось 9,71666666666667, что означает что точность работы таймера/вызова_ивента по даному таймеру в 10 раз выше требуемой, а значит искать замену ему нет нужды.
Это же означает что если возникают проблемы у тебя на твоей задаче, то это:
- или проблема железа
- или проблема неоптимального кода. Например, того что данные обрабатываются в основном потоке, что тормозит сам таймер. И использовав любую другую реализацию ивентов ты получишь все те же самые проблемы.
- 19,097
Так как детальных требований нет, то могу предложить такой вариант, который решает проблему подвисания UI или ожидания выполнения прошлого вызова до возможности проверить настало ли время обработки следующего.
Если скорость выполнения метода может быть долгой, временные метки находятся очень близко друг к другу и они вызывают один и тот же код, то можно использовать класс из другого моего ответа, который позволяет игнорировать вызовы, если метод ещё не выполнился или ставить их в очередь: https://ru.stackoverflow.com/a/940044/185863
Если события не планируется добавлять в ходе работы программы, то lock(...) вообще можно убрать.
EventInfo.cs
using System;
namespace WindowsFormsApp1
{
public class EventInfo
{
public DateTime Time { get; }
public EventInfo(DateTime time)
{
Time = time;
}
}
}
EventManager.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace WindowsFormsApp1
{
/// <summary>
/// Нужен лишь для того, чтобы при подписке в параметрах метода было автоматом EventInfo info,
/// а не EventInfo obj, что избавляет от необходимости его переименовывать.
/// </summary>
public delegate void EventInfoHandler(EventInfo info);
public class EventManager
{
private readonly object _sync;
private readonly List<EventInfo> _events;
private readonly SynchronizationContext _syncContext;
private CancellationTokenSource _cancellation;
public event EventInfoHandler Triggered;
public EventManager(bool captureCurrentSyncContext)
{
// Экземпляр текущего класса нужно создавать в UI потоке, чтобы событие Triggered
// так же срабатывало в UI потоке, если это необходимо. Так же captureCurrentSyncContext
// должен быть true
if (captureCurrentSyncContext)
{
_syncContext = SynchronizationContext.Current ?? new SynchronizationContext();
}
else
{
// Контекст синхронизации пула потоков
_syncContext = new SynchronizationContext();
}
_sync = new object();
_events = new List<EventInfo>();
_cancellation = new CancellationTokenSource();
}
public async void Start()
{
try
{
await Task.Factory.StartNew(async () =>
{
while (!_cancellation.IsCancellationRequested)
{
lock (_sync)
{
DateTime now = DateTime.Now;
for (int i = 0; i < _events.Count; i++)
{
if (now >= _events[i].Time)
{
OnTriggered(_events[i]);
_events.RemoveAt(i);
--i;
}
}
}
await Task.Delay(50, _cancellation.Token);
}
_cancellation.Token.ThrowIfCancellationRequested();
}, TaskCreationOptions.LongRunning);
}
catch (OperationCanceledException ex)
{
_cancellation.Dispose();
_cancellation = null;
// Логируем, если нужно
}
catch (Exception ex)
{
Debug.WriteLine(ex); // Используем свой логгер, это просто пример
}
}
// Для остановки 1 раз перед закрытие приложения
public void Stop()
{
_cancellation.Cancel();
}
public void Add(EventInfo eventInfo)
{
lock (_sync)
{
_events.Add(eventInfo);
}
}
public void Add(IEnumerable<EventInfo> events)
{
lock (_sync)
{
_events.AddRange(events);
}
}
/// <summary>
/// Инициирует срабатывание события не дожидаясь его обработки у подписчиков.
/// </summary>
/// <param name="info"></param>
protected void OnTriggered(EventInfo info)
{
var handler = Triggered;
if (handler != null)
{
// Отправляем уведомление о событии и забываем (асинхронно)
_syncContext.Post((unused) =>
{
handler.Invoke(info);
}, null);
}
}
}
}
Program.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
static class Program
{
public static EventManager EventManager { get; private set; }
/// <summary>
/// Главная точка входа для приложения.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
// Не придумал, как захватить контекст синхронизации UI создав
// экземпляр класса здесь.
public static void Initialize()
{
if (EventManager != null)
{
return;
}
EventManager = new EventManager(true);
EventManager.Triggered += OnEventManagerTriggered;
EventManager.Add(new EventInfo(DateTime.Now.AddSeconds(5)));
EventManager.Add(new EventInfo(DateTime.Now.AddSeconds(8)));
EventManager.Add(new EventInfo(DateTime.Now.AddSeconds(10)));
EventManager.Start();
}
private static void OnEventManagerTriggered(EventInfo info)
{
Debug.WriteLine(info.Time.ToLongTimeString());
}
}
}
MainForm.cs
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void OnMainFormLoad(object sender, EventArgs e)
{
Program.Initialize();
Program.EventManager.Triggered += OnEventManagerTriggered;
}
private void OnEventManagerTriggered(EventInfo info)
{
_listBox.Items.Add(info.Time.ToLongTimeString());
}
}
}
- 1,228
- 9
- 19
System.Windows.Forms.Timer. События этого таймера работают в GUI-потоке. Если они длительные, то форма замерзает и т. п. Тогда вам действительно нужно обрабатывать события асинхронно, в другом потоке. – Alexander Petrov Feb 03 '19 at 18:26