Как исправить замерзание формы при её создании не из главного потока
В чём суть проблемы: есть статичный класс, который реализует очередь уведомлений, отправленных из главной формы. Из этого класса AlertThread следит за потокобезопасной коллекцией ConcurrentQueue<...> AlertQueue. При поступлении уведомлений достаём первый в очереди элемент AlertQueue.TryDequeue(out alert) и в этом же потоке создаём экземпляр формы FormAlert уведомления и показываем пользователю.
Всё по отдельности работает замечательно, как часы, но вот вместе не работает. Создание формы таким образом приводит к тому, что она просто замерзает, хотя поток работает исправно. Когда-то давно пробовал асинхронно вызывать форму, без очереди, и пробовал отдельный event писать, и потом из разных частей кода вызывать, но всегда получал замороженную форму, которая вообще никак не реагировала. Я её даже завершить не мог, только завершение отладки в VS помогало.
Так вот, как это можно исправить? Что мешает форме работать в другом потоке?
Я использую .NET Framework 4.8
Класс Alert:
public static class Alert
{
private static bool IsWork = false;
private static Thread AlertThread;
private static ConcurrentQueue<(string, enumType)> AlertQueue = new ConcurrentQueue<(string, enumType)>();
private static int MaxAlerts;
public static void Start()
{
IsWork = true;
AlertThread = new Thread(AlertInvoker)
{
IsBackground = true,
Name = "AlertThread BubbleAnimation",
Priority = ThreadPriority.Normal
};
AlertThread.Start();
}
public static void Suspend()
{
if (AlertThread.IsAlive)
IsWork = false;
}
public static void Resume()
{
if (AlertThread.IsAlive)
IsWork = true;
}
private static void AlertInvoker()
{
while (IsWork)
{
if (AlertQueue.Count < MaxAlerts && AlertQueue.Count > 0)
{
(string, enumType) alert;
if (AlertQueue.TryDequeue(out alert))
{
FormAlert falert = new FormAlert();
falert.showAlert(alert.Item1, alert.Item2, MaxAlerts);
}
}
Thread.Sleep(10);
}
}
public static void AddAlert(string msg, enumType type)
{
AlertQueue.Enqueue((msg, type));
MaxAlerts = (int)Math.Round((Screen.PrimaryScreen.WorkingArea.Height - 75.0) / 75.0);
}
}
Класс формы уведомления FormAlert:
public partial class FormAlert : Form
{
public FormAlert()
{
InitializeComponent();
buttonClose.Click += (obj, a) => buttonClose_Click(obj, a);
timer1.Tick += (obj, a) => timer1_Tick(obj, a);
}
public enum enumActions
{
wait,
start,
close
}
public enum enumType
{
Succsess,
Warning,
Error,
Info
}
private FormAlert.enumActions action;
private int x, y;
private void buttonClose_Click(object sender, EventArgs e)
{
timer1.Interval = 1;
action = enumActions.close;
}
private void timer1_Tick(object sender, EventArgs e)
{
switch (this.action)
{
case enumActions.wait:
timer1.Interval = 5000;
action = enumActions.close;
break;
case enumActions.start:
timer1.Interval = 1;
if (this.Opacity <= 0.7)
this.Opacity += 0.1;
if (this.x < this.Location.X)
{
this.Left--;
}
else
{
if (this.Opacity >= 0.7)
{
action = enumActions.wait;
}
}
break;
case enumActions.close:
timer1.Interval = 1;
this.Opacity -= 0.1;
this.Left -= 3;
if (base.Opacity <= 0.0)
{
base.Close();
}
break;
}
}
public void showAlert(string msg, enumType type, int m_alert)
{
this.Opacity = 0.0;
this.StartPosition = FormStartPosition.Manual;
string fname;
for (int i = 0; i < m_alert - 1; i++)
{
fname = "alert" + i.ToString();
if ((FormAlert)Application.OpenForms[fname] == null)
{
this.Name = fname;
this.Text = type.ToString();
this.x = Screen.PrimaryScreen.WorkingArea.Width - this.Width + 15;
this.y = Screen.PrimaryScreen.WorkingArea.Height - this.Height * i - 75;
this.Location = new Point(this.x, this.y);
break;
}
}
this.x = Screen.PrimaryScreen.WorkingArea.Width - base.Width;
switch (type)
{
case enumType.Succsess:
this.picture.Image = Resources.success_g;
break;
case enumType.Warning:
this.picture.Image = Resources.warning_r;
break;
case enumType.Error:
this.picture.Image = Resources.error_y;
break;
case enumType.Info:
this.picture.Image = Resources.info_b;
break;
}
if (msg.Length <= 24)
{
this.message.Text = msg;
}
else if (msg.Length > 24 && msg.Length <= 48)
{
this.message.AutoSize = false;
this.message.MinimumSize = new Size(230, 42);
this.message.Location = new Point(this.message.Location.X, this.message.Location.Y - 12);
this.message.Text = msg;
}
else if (msg.Length > 48)
{
this.message.AutoSize = false;
this.message.MinimumSize = new Size(230, 63);
this.message.Location = new Point(this.message.Location.X, this.message.Location.Y - 24);
this.message.Text = msg;
}
this.Show();
this.action = enumActions.start;
this.timer1.Interval = 1;
timer1.Start();
}
}
Ответы (1 шт):
Формы не работают в потоке без message loop, который создается функцией Application.Run: https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.application.run?view=netcore-3.1
Стандартное начало программы WinForms:
Application.Run(new Form1());
Эта строчка создает message loop для формы. Без него форма мертвая.
Можно попробовать запустить message loop в другом потоке, но это создает много проблем. Пример здесь: https://stackoverflow.com/questions/4698080/spawn-a-new-thread-to-open-a-new-window-and-close-it-from-a-different-thread
Я рекомендую создавать формы только в главном потоке, а другие задачи делать в рабочих потоках. UI по своей природе однопоточный, его объекты работают с экраном, мышью и клавиатурой, никакого преимущества от потоков в UI нет.