C# 跨线程操作无效:从创建控件的线程以外的线程访问控件
我有一个设想。(Windows窗体、C#、.NET)C# 跨线程操作无效:从创建控件的线程以外的线程访问控件,c#,multithreading,winforms,invoke,C#,Multithreading,Winforms,Invoke,我有一个设想。(Windows窗体、C#、.NET) 有一个主窗体承载一些用户控件 用户控件执行一些繁重的数据操作,因此,如果我直接调用UserControl\u Load方法,用户界面在Load方法执行期间将变得无响应 为了克服这个问题,我在不同的线程上加载数据(尽可能少地修改现有代码) 我使用了一个后台工作线程,它将加载数据,完成后将通知应用程序它已经完成了工作 现在出现了一个真正的问题。所有UI(主窗体及其子用户控件)都是在主线程上创建的。在usercontrol的LOAD方法中,我根据u
UserControl\u Load
方法,用户界面在Load方法执行期间将变得无响应UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
UserContrl1\u LoadDataMethod()
{
if(textbox1.text==“MyName”)//这会产生异常
{
//加载与“MyName”对应的数据。
//填充一个globale变量列表,该列表将在稍后的某个阶段绑定到网格。
}
}
它给出的例外是
跨线程操作无效:从创建控件的线程以外的线程访问控件
为了了解更多信息,我在谷歌上搜索了一下,并提出了一个建议,比如使用以下代码
代码2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
UserContrl1\u LoadDataMethod()
{
if(invokererequired)//第1行
{
Invoke(newmethodinvoker(UserContrl1_LoadDataMethod));
返回;
}
if(textbox1.text==“MyName”)//现在它不会给出异常
{
//加载与“MyName”对应的数据
//填充globale变量列表,该列表将在稍后的某个阶段绑定到网格
}
}
但是但是。。。看来我又回到原点了。再次申请
变得没有反应。这似乎是由于执行了第#1行if条件。加载任务再次由父线程完成,而不是由我生成的第三个线程完成
我不知道我是否认为这是对的还是错的。我是穿线新手
如何解决此问题,以及执行第#1行if块的效果如何
情况是这样的:我希望根据控件的值将数据加载到全局变量中。我不想更改子线程中控件的值。我永远不会从一个孩子的角度来做这件事
因此,只有访问该值,才能从数据库中获取相应的数据。NET中的控件通常不是线程安全的。这意味着您不应该从它所在的线程之外的线程访问控件。为了解决这个问题,您需要调用控件,这是您的第二个示例正在尝试的
但是,在本例中,您所做的只是将长时间运行的方法传递回主线程。当然,这不是你真正想做的。您需要重新考虑一下这一点,以便在主线程上执行的所有操作都是在此处或此处设置quick属性。您只想使用
Invoke
或BeginInvoke
来完成更改UI所需的最低工作量。您的“重”方法应该在另一个线程上执行(例如,通过BackgroundWorker
),然后使用控件。调用/控件。开始调用只是为了更新UI。这样你的UI线程就可以自由处理UI事件等
请看我的文章,尽管这篇文章是在BackgroundWorker
到达现场之前写的,但我恐怕还没有更新这方面的内容BackgroundWorker
只是稍微简化了回调。根据(自删除后):
我想我没有恰当地提出这个问题
情况是这样的:我想根据控件的值将数据加载到全局变量中。我不想更改子线程中控件的值。我永远不会从一个孩子的角度来做这件事
因此,只有访问该值,才能从数据库中获取相应的数据
然后,您需要的解决方案应该如下所示:
UserContrl1_LOadDataMethod()
{
string name = "";
if(textbox1.InvokeRequired)
{
textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
}
if(name == "MyName")
{
// do whatever
}
}
在尝试切换回控件的线程之前,请在单独的线程中进行认真的处理。例如:
UserContrl1_LOadDataMethod()
{
if(textbox1.text=="MyName") //<<======Now it wont give exception**
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be
//bound to grid at some later stage
if(InvokeRequired)
{
// after we've done all the processing,
this.Invoke(new MethodInvoker(delegate {
// load the control with the appropriate data
}));
return;
}
}
}
UserContrl1\u LOadDataMethod()
{
如果(textbox1.text==“MyName”)/您需要查看Backgroundworker示例:
特别是它与UI层的交互方式。根据您的帖子,这似乎回答了您的问题。我在FileSystemWatcher中遇到了这个问题,并发现以下代码解决了这个问题:
fsw.synchroningobject=此
控件然后使用当前表单对象来处理事件,因此将处于同一线程上。对于UI跨线程问题,最干净(也是最合适)的解决方案是使用SynchronizationContext,请参阅文章,它对其进行了很好的解释。如果您正在处理的对象没有
(InvokeRequired)
如果您使用的是类中的主窗体,而不是主窗体,并且对象位于主窗体中,但不需要invokererequired,则这非常有用
delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);
private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}
public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
objectWithoutInvoke.Text = text;
}
它的工作原理与上面相同,但如果您没有需要invokerequired的对象,但确实可以访问MainForm,则这是一种不同的方法。在xamarin studio之外的visual studio winforms原型项目中编程iOS Phone monotouch应用程序控制器时,我发现有必要这样做。更喜欢在VS中编程,而不是在xamarin st中编程我希望控制器尽可能地与手机框架完全解耦。这样,在其他框架(如Android和Windows phone)中实现这一点对于未来的使用来说会容易得多
我想要一个解决方案,GUI可以用
public partial class Form1 : Form
{
private ExampleController.MyController controller;
public Form1()
{
InitializeComponent();
controller = new ExampleController.MyController((ISynchronizeInvoke) this);
controller.Finished += controller_Finished;
}
void controller_Finished(string returnValue)
{
label1.Text = returnValue;
}
private void button1_Click(object sender, EventArgs e)
{
controller.SubmitTask("Do It");
}
}
public delegate void FinishedTasksHandler(string returnValue);
public class MyController
{
private ISynchronizeInvoke _syn;
public MyController(ISynchronizeInvoke syn) { _syn = syn; }
public event FinishedTasksHandler Finished;
public void SubmitTask(string someValue)
{
System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
}
private void submitTask(string someValue)
{
someValue = someValue + " " + DateTime.Now.ToString();
System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.
if (Finished != null)
{
if (_syn.InvokeRequired)
{
_syn.Invoke(Finished, new object[] { someValue });
}
else
{
Finished(someValue);
}
}
}
}
/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
{
a();
}));
else return false;
return true;
}
// usage on textbox
public void UpdateTextBox1(String text)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
textBox1.Text = ellapsed;
}
//Or any control
public void UpdateControl(Color c, String s)
{
//Check if invoke requied if so return - as i will be recalled in correct thread
if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
myControl.Text = s;
myControl.BackColor = c;
}
public static class Extensions
{
public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del)
where TControlType : Control
{
if (control.InvokeRequired)
control.Invoke(new Action(() => del(control)));
else
del(control);
}
}
textbox1.Invoke(t => t.Text = "A");
using System.Threading.Tasks;
using System.Threading;
namespace TESTE
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Action<string> DelegateTeste_ModifyText = THREAD_MOD;
Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
}
private void THREAD_MOD(string teste)
{
textBox1.Text = teste;
}
}
}
/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
/// <summary>
/// No more delegates, background workers etc. just one line of code as shown below
/// Note it is dependent on the XTask class shown next.
/// </summary>
public async void ExampleMethod()
{
//Still on GUI/Original Thread here
//Do your updates before the next line of code
await XTask.RunAsync(() =>
{
//Running an asynchronous task here
//Cannot update GUI Thread here, but can do lots of work
});
//Can update GUI/Original thread on this line
}
}
/// <summary>
/// A class containing extension methods for the Task class
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
/// <summary>
/// RunAsync is an extension method that encapsulates the Task.Run using a callback
/// </summary>
/// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
/// <returns></returns>
public async static Task RunAsync(Action Code)
{
await Task.Run(() =>
{
Code();
});
return;
}
}
/// <summary>
/// Run Async
/// </summary>
/// <typeparam name="T">The type to return</typeparam>
/// <param name="Code">The callback to the code</param>
/// <param name="Error">The handled and logged exception if one occurs</param>
/// <returns>The type expected as a competed task</returns>
public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
{
var done = await Task<T>.Run(() =>
{
T result = default(T);
try
{
result = Code("Code Here");
}
catch (Exception ex)
{
Console.WriteLine("Unhandled Exception: " + ex.Message);
Console.WriteLine(ex.StackTrace);
Error(ex);
}
return result;
});
return done;
}
public async void HowToUse()
{
//We now inject the type we want the async routine to return!
var result = await RunAsync<bool>((code) => {
//write code here, all exceptions are logged via the wrapped try catch.
//return what is needed
return someBoolValue;
},
error => {
//exceptions are already handled but are sent back here for further processing
});
if (result)
{
//we can now process the result because the code above awaited for the completion before
//moving to this statement
}
}
public class data_holder_for_controls
{
//it will hold value for your label
public string status = string.Empty;
}
class Demo
{
public static data_holder_for_controls d1 = new data_holder_for_controls();
static void Main(string[] args)
{
ThreadStart ts = new ThreadStart(perform_logic);
Thread t1 = new Thread(ts);
t1.Start();
t1.Join();
//your_label.Text=d1.status; --- can access it from any thread
}
public static void perform_logic()
{
//put some code here in this function
for (int i = 0; i < 10; i++)
{
//statements here
}
//set result in status variable
d1.status = "Task done";
}
}
CheckForIllegalCrossThreadCalls = false
Invoke(new Action(() =>
{
label1.Text = "WooHoo!!!";
}));
this.Invoke(new MethodInvoker(delegate
{
//your code here;
}));
Control.InvokeRequired Property
SynchronizationContext Post Method
this.Invoke((MethodInvoker)delegate
{
YourControl.Property= value; // runs thread safe
});
public static class FormExts
{
public static void LoadOnUI(this Form frm, Action action)
{
if (frm.InvokeRequired) frm.Invoke(action);
else action.Invoke();
}
}
private void OnAnyEvent(object sender, EventArgs args)
{
this.LoadOnUI(() =>
{
label1.Text = "";
button1.Text = "";
});
}