c#中invokerequired偶尔会出现问题
occasionally problems with invokerequired in c#
我时不时遇到 invokerequired 方法的问题,想问问是否有人知道这个问题或者可以向我解释一下。我有一些代码:
splashScreen splashObj = splashScreen.GetInstance();
if (thrd == null)
{
thrd = new Thread(new ThreadStart(loadingScreenStart));
thrd.IsBackground = true;
thrd.Start();
}
else
{
if (splashObj.InvokeRequired)
{
splashObj.Invoke((MethodInvoker)delegate()
{
splashObj.Show();
}
);
}
else
{
splashObj.Show();
}
}
if (splashObj.loadLabel.InvokeRequired)
{
splashObj.loadLabel.Invoke((MethodInvoker)delegate()
{
splashObj.loadLabel.Text = "Checking Username...";
}
);
}
else
{
splashObj.loadLabel.Text = "Checking Username...";
}
public void loadingScreenStart()
{
splashScreen splashObj = splashScreen.GetInstance();
Application.Run(splashScreen.GetInstance());
}
启动画面代码:
public partial class splashScreen : Form
{
public Form1 form1;
public splashScreen()
{
InitializeComponent();
//form1 = frm1;
}
public splashScreen(Form1 frm1)
{
form1 = frm1;
}
private void splashScreen_Load(object sender, EventArgs e)
{
}
private static splashScreen m_instance = null;
private static object m_instanceLock = new object();
public static splashScreen GetInstance()
{
lock (m_instanceLock)
{
if (m_instance == null)
{
m_instance = new splashScreen();
}
}
return m_instance;
}
}
因此,当其余代码在后台加载时,我会显示启动画面。消息显示为 "Checking Username..."、"Checking Password..."、"Loading..." 等。
这段代码工作正常,但有时似乎代码执行速度比线程快,然后我得到一个错误,该方法是从另一个线程而不是创建它的线程调用的。为什么会这样?这种问题有什么解决办法吗?这种情况可能在二十次处决中发生一次。
多线程很难,所以你应该尽量让它变得简单。第一,没有理由在启动画面 class 本身之外包含任何启动画面代码。其次,您应该始终知道自己在做什么,所以 InvokeRequired
只是一种代码味道,表示 "I have no idea who's going to call this method".
void Main()
{
SplashScreen.ShowText("Loading 1");
Thread.Sleep(1000);
SplashScreen.ShowText("Loading 2");
Thread.Sleep(2000);
SplashScreen.Done();
Thread.Sleep(2000);
SplashScreen.ShowText("Loading 3");
}
// Define other methods and classes here
public partial class SplashScreen : Form
{
private static SplashScreen instance;
private static readonly ManualResetEvent initEvent = new ManualResetEvent(false);
Label loadLabel;
private SplashScreen()
{
// InitializeComponent();
loadLabel = new Label();
Controls.Add(loadLabel);
Load += (s, e) => initEvent.Set();
Closing += (s, e) => initEvent.Reset();
}
private static object syncObject = new object();
private static void InitializeIfRequired()
{
// If not set, we'll have to init the message loop
if (!initEvent.WaitOne(0))
{
lock (syncObject)
{
// Someone initialized it before us
if (initEvent.WaitOne(0)) return;
// Recreate the form if it was closed
instance = new SplashScreen();
var thread = new Thread(() => { Application.Run(instance); });
thread.Start();
// Wait until the form is ready
initEvent.WaitOne();
}
}
}
public static void ShowText(string text)
{
InitializeIfRequired();
instance.Invoke((Action)(() =>
{
if (!instance.IsDisposed) instance.loadLabel.Text = text;
}
));
}
public static void Done()
{
// Is it closed already?
if (!initEvent.WaitOne(0)) return;
lock (syncObject)
{
// Someone closed it before us
if (!initEvent.WaitOne(0)) return;
instance.Invoke((Action)(() => { instance.Close(); }));
}
}
}
这是 LINQPad 的片段,您需要删除 InitializeComponent
以及 loadLabel
控件(应该在设计器上)的注释。
这样,您就可以将启动画面的逻辑与应用程序的其余部分完全隔离开来。要显示闪屏文本,只需调用 SplashScreen.ShowText
。要使启动画面消失,请调用 SplashScreen.Done
.
请注意如何不需要使用 InvokeRequired
- SplashScreen.ShowText
(或 Done
)的任何合法调用都不会 not 需要编组到初始屏幕的 UI 线程。
现在,这并不完美。这是我在大约 10 分钟内写的东西。但它(可能 :))是线程安全的,比原来的更好地遵循最佳实践,并且更易于使用。
此外,在某些情况下我会使用更高级别的结构,例如Lazy<T>
和 Task
- 但由于这在这里并没有真正帮助(开始新的消息传递循环,如果关闭则必须重新创建表单......),我选择了更简单的解决方案。
请注意,我使用的是 Invoke
而不是 BeginInvoke
或类似的 - 这非常重要,因为否则可能会排队关闭,然后是 ShowText
这将适用于处置形式。如果您打算从多个线程调用 ShowText
,那么也锁定整个 ShowText
主体会更安全。考虑到启动画面的常见用例,有一些线程安全是不必要的,但是...
好的,所以这个问题仍然存在,当您的线程在显示之前调用表单时可能会发生; InvokeRequired 和 BeginInvoke 都可能失败。
一个解决方案是在表单事件 Shown:
上启动线程
/// <summary>
/// Testing form
/// </summary>
public partial class MyForm : Form
{
/// <summary>
/// Work work
/// </summary>
private Thread _MyThread;
/// <summary>
/// New test form
/// </summary>
public MyForm()
{
// Initialize components
this.InitializeComponent();
}
/// <summary>
/// First time form is show
/// </summary>
private void MyForm_Shown(object sender, EventArgs e)
{
// Create and start thread
this._MyThread = new Thread(this.MyThreadWork);
this._MyThread.Start();
}
/// <summary>
/// Doing some work here
/// </summary>
private void MyThreadWork()
{
// Counting time
int count = 0;
// Forever and ever
while (true)
{
this.PrintInfo((count++).ToString());
Thread.Sleep(1000);
}
}
/// <summary>
/// Printing info into form title
/// </summary>
private void PrintInfo(string info)
{
// Needs to sync?
if (this.InvokeRequired)
{
// Sync me
this.BeginInvoke(new Action<string>(this.PrintInfo), info);
}
else
{
// Prints info
this.Text = info;
}
}
/// <summary>
/// Good bye form, don't miss the work work
/// </summary>
private void MyForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Kill worker
this._MyThread.Abort();
}
}
有些会在第一个 PrintInfo 之前添加一个 Thread.Sleep 但很常见,您会使用什么睡眠时间? 1、10、100?取决于您的表单显示的速度。
我时不时遇到 invokerequired 方法的问题,想问问是否有人知道这个问题或者可以向我解释一下。我有一些代码:
splashScreen splashObj = splashScreen.GetInstance();
if (thrd == null)
{
thrd = new Thread(new ThreadStart(loadingScreenStart));
thrd.IsBackground = true;
thrd.Start();
}
else
{
if (splashObj.InvokeRequired)
{
splashObj.Invoke((MethodInvoker)delegate()
{
splashObj.Show();
}
);
}
else
{
splashObj.Show();
}
}
if (splashObj.loadLabel.InvokeRequired)
{
splashObj.loadLabel.Invoke((MethodInvoker)delegate()
{
splashObj.loadLabel.Text = "Checking Username...";
}
);
}
else
{
splashObj.loadLabel.Text = "Checking Username...";
}
public void loadingScreenStart()
{
splashScreen splashObj = splashScreen.GetInstance();
Application.Run(splashScreen.GetInstance());
}
启动画面代码:
public partial class splashScreen : Form
{
public Form1 form1;
public splashScreen()
{
InitializeComponent();
//form1 = frm1;
}
public splashScreen(Form1 frm1)
{
form1 = frm1;
}
private void splashScreen_Load(object sender, EventArgs e)
{
}
private static splashScreen m_instance = null;
private static object m_instanceLock = new object();
public static splashScreen GetInstance()
{
lock (m_instanceLock)
{
if (m_instance == null)
{
m_instance = new splashScreen();
}
}
return m_instance;
}
}
因此,当其余代码在后台加载时,我会显示启动画面。消息显示为 "Checking Username..."、"Checking Password..."、"Loading..." 等。 这段代码工作正常,但有时似乎代码执行速度比线程快,然后我得到一个错误,该方法是从另一个线程而不是创建它的线程调用的。为什么会这样?这种问题有什么解决办法吗?这种情况可能在二十次处决中发生一次。
多线程很难,所以你应该尽量让它变得简单。第一,没有理由在启动画面 class 本身之外包含任何启动画面代码。其次,您应该始终知道自己在做什么,所以 InvokeRequired
只是一种代码味道,表示 "I have no idea who's going to call this method".
void Main()
{
SplashScreen.ShowText("Loading 1");
Thread.Sleep(1000);
SplashScreen.ShowText("Loading 2");
Thread.Sleep(2000);
SplashScreen.Done();
Thread.Sleep(2000);
SplashScreen.ShowText("Loading 3");
}
// Define other methods and classes here
public partial class SplashScreen : Form
{
private static SplashScreen instance;
private static readonly ManualResetEvent initEvent = new ManualResetEvent(false);
Label loadLabel;
private SplashScreen()
{
// InitializeComponent();
loadLabel = new Label();
Controls.Add(loadLabel);
Load += (s, e) => initEvent.Set();
Closing += (s, e) => initEvent.Reset();
}
private static object syncObject = new object();
private static void InitializeIfRequired()
{
// If not set, we'll have to init the message loop
if (!initEvent.WaitOne(0))
{
lock (syncObject)
{
// Someone initialized it before us
if (initEvent.WaitOne(0)) return;
// Recreate the form if it was closed
instance = new SplashScreen();
var thread = new Thread(() => { Application.Run(instance); });
thread.Start();
// Wait until the form is ready
initEvent.WaitOne();
}
}
}
public static void ShowText(string text)
{
InitializeIfRequired();
instance.Invoke((Action)(() =>
{
if (!instance.IsDisposed) instance.loadLabel.Text = text;
}
));
}
public static void Done()
{
// Is it closed already?
if (!initEvent.WaitOne(0)) return;
lock (syncObject)
{
// Someone closed it before us
if (!initEvent.WaitOne(0)) return;
instance.Invoke((Action)(() => { instance.Close(); }));
}
}
}
这是 LINQPad 的片段,您需要删除 InitializeComponent
以及 loadLabel
控件(应该在设计器上)的注释。
这样,您就可以将启动画面的逻辑与应用程序的其余部分完全隔离开来。要显示闪屏文本,只需调用 SplashScreen.ShowText
。要使启动画面消失,请调用 SplashScreen.Done
.
请注意如何不需要使用 InvokeRequired
- SplashScreen.ShowText
(或 Done
)的任何合法调用都不会 not 需要编组到初始屏幕的 UI 线程。
现在,这并不完美。这是我在大约 10 分钟内写的东西。但它(可能 :))是线程安全的,比原来的更好地遵循最佳实践,并且更易于使用。
此外,在某些情况下我会使用更高级别的结构,例如Lazy<T>
和 Task
- 但由于这在这里并没有真正帮助(开始新的消息传递循环,如果关闭则必须重新创建表单......),我选择了更简单的解决方案。
请注意,我使用的是 Invoke
而不是 BeginInvoke
或类似的 - 这非常重要,因为否则可能会排队关闭,然后是 ShowText
这将适用于处置形式。如果您打算从多个线程调用 ShowText
,那么也锁定整个 ShowText
主体会更安全。考虑到启动画面的常见用例,有一些线程安全是不必要的,但是...
好的,所以这个问题仍然存在,当您的线程在显示之前调用表单时可能会发生; InvokeRequired 和 BeginInvoke 都可能失败。
一个解决方案是在表单事件 Shown:
上启动线程/// <summary>
/// Testing form
/// </summary>
public partial class MyForm : Form
{
/// <summary>
/// Work work
/// </summary>
private Thread _MyThread;
/// <summary>
/// New test form
/// </summary>
public MyForm()
{
// Initialize components
this.InitializeComponent();
}
/// <summary>
/// First time form is show
/// </summary>
private void MyForm_Shown(object sender, EventArgs e)
{
// Create and start thread
this._MyThread = new Thread(this.MyThreadWork);
this._MyThread.Start();
}
/// <summary>
/// Doing some work here
/// </summary>
private void MyThreadWork()
{
// Counting time
int count = 0;
// Forever and ever
while (true)
{
this.PrintInfo((count++).ToString());
Thread.Sleep(1000);
}
}
/// <summary>
/// Printing info into form title
/// </summary>
private void PrintInfo(string info)
{
// Needs to sync?
if (this.InvokeRequired)
{
// Sync me
this.BeginInvoke(new Action<string>(this.PrintInfo), info);
}
else
{
// Prints info
this.Text = info;
}
}
/// <summary>
/// Good bye form, don't miss the work work
/// </summary>
private void MyForm_FormClosed(object sender, FormClosedEventArgs e)
{
// Kill worker
this._MyThread.Abort();
}
}
有些会在第一个 PrintInfo 之前添加一个 Thread.Sleep 但很常见,您会使用什么睡眠时间? 1、10、100?取决于您的表单显示的速度。