Bey*_*heP 5 c# multithreading listbox bindinglist winforms
我刚刚学习 C#/.NET,我遇到了这个问题。
所以在我的解决方案中,我有 2 个项目:winforms UI 和带有逻辑的 dll。在 dll 中,我有 BindingList,它为 UI 中的 listBox 提供数据源。
用户界面:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
InitializeComponent();
_class1 = new Class1(); // logic class insatce
listBox1.DataSource = _class1.BindingList;
}
private void button1_Click(object sender, EventArgs e)
{
_class1.Add();
}
private void button2_Click(object sender, EventArgs e)
{
_class1.Remove();
}
}
Run Code Online (Sandbox Code Playgroud)
逻辑类:
public class Class1
{
public BindingList<string> BindingList { get; set; } = new BindingList<string>() ;
public void Add()
{
var th = new Thread(() =>
{
lock (BindingList)
{
BindingList.Add("1");
}
}) {IsBackground = true};
th.Start();
// works fine
//BindingList.Add("1");
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以问题是,如果我只是运行解决方案(ctrl + F5)一切正常,但在调试模式(F5)中,当我按下按钮时没有任何反应。我找到的所有答案都说:“使用锁”所以我使用了锁和列表框仍然没有对向列表添加元素做出反应。请帮助我我做错了什么或我错过了什么。
PS对不起我的英语。
首先,要明确:您可能需要也可能不需要在lock这里使用。这将取决于实际上是否有两个或多个线程同时访问该BindingList<T>对象,即实际上是同时访问(例如,两个或多个线程向列表添加项目,或者一个线程添加项目而另一个线程试图从列表中读取) )。在您的代码示例中,情况似乎并非如此,因此没有必要。无论如何,该lock语句与解决您所询问的特定问题所需的内容完全不同,并且在任何情况下都只在线程lock在同一对象上协作使用时才有效(如果只有一个线程调用lock,那没有帮助) .
基本问题是当这些事件在 UI 线程以外的线程上引发时ListBox无法响应事件BindingList。通常,对此的解决方案是Control.Invoke()在 UI 线程中调用或类似地执行列表修改操作。但是在您的情况下,拥有 的类BindingList不是 UI 对象,因此自然无法访问该Control.Invoke()方法。
恕我直言,最好的解决方案是保留所涉及的 UI 对象中的 UI 线程知识。但是这样做需要让Class1对象至少将列表的某些控制权移交给该 UI 对象。一种这样的方法将涉及向Class1对象添加一个事件:
public class AddItemEventArgs<T> : EventArgs
{
public T Item { get; private set; }
public AddItemEventArgs(T item)
{
Item = item;
}
}
public class Class1
{
public EventHandler<AddItemEventArgs<string>> AddItem;
public BindingList<string> BindingList { get; set; }
public Class1()
{
// Sorry, old-style because I'm not using C# 6 yet
BindingList = new BindingList<string>();
}
// For testing, I prefer unique list items
private int _index;
public void Add()
{
var th = new Thread(() =>
{
string item = (++_index).ToString();
OnAddItem(item);
}) { IsBackground = true };
th.Start();
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
private void OnAddItem(string item)
{
EventHandler<AddItemEventArgs<string>> handler = AddItem;
if (handler != null)
{
handler(this, new AddItemEventArgs<string>(item));
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后在您的Form1:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
InitializeComponent();
_class1 = new Class1(); // logic class instance
_class1.AddItem += (sender, e) =>
{
Invoke((MethodInvoker)(() => _class1.BindingList.Add(e.Item)));
};
listBox1.DataSource = _class1.BindingList;
}
private void button1_Click(object sender, EventArgs e)
{
_class1.Add();
}
private void button2_Click(object sender, EventArgs e)
{
_class1.Remove();
}
}
Run Code Online (Sandbox Code Playgroud)
这个主题的一个变体是在Class1. 第一个将是您现在拥有的,最终使用线程。第二个是需要从 UI 线程调用的,它实际上会添加项目。在AddItem表单中的事件处理程序中,不是将项目直接添加到列表中,而是调用第二个“add”方法来为表单执行此操作。
哪个最好取决于您想要在Class1. 如果您试图对其他类隐藏列表及其操作,那么这种变化会更好。但是如果你不介意从Class1代码以外的地方更新列表,上面的代码示例应该没问题。
另一种方法是使您的Class1对象线程感知,类似于 eg 的BackgroundWorker工作方式。您可以通过在创建对象SynchronizationContext时捕获线程的当前来完成此操作Class1(假设Class1对象是在您要返回的线程中创建的,以添加项目)。然后在添加项目时,使用该上下文对象进行添加。
看起来像这样:
public class Class1
{
public BindingList<string> BindingList { get; set; }
private readonly SynchronizationContext _context = SynchronizationContext.Current;
public Class1()
{
BindingList = new BindingList<string>();
}
private int _index;
public void Add()
{
var th = new Thread(() =>
{
string item = (++_index).ToString();
_context.Send(o => BindingList.Add(item), null);
}) { IsBackground = true };
th.Start();
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
}
Run Code Online (Sandbox Code Playgroud)
在此版本中,不需要更改Form1。
这个基本方案有很多变体,包括一些将逻辑放入专门的BindingList<T>子类中的变体。例如(举几个例子):
跨线程表单绑定 - 可以做到吗?
BindingList<> ListChanged 事件
最后,如果你真的想把东西混在一起,你可以在列表发生变化时强制重置整个绑定。在这种情况下,您不需要更改Class1,但您需要更改Form1:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
bool adding = false;
InitializeComponent();
_class1 = new Class1(); // logic class instance
_class1.BindingList.ListChanged += (sender, e) =>
{
Invoke((MethodInvoker)(() =>
{
if (e.ListChangedType == ListChangedType.ItemAdded && !adding)
{
// Remove and re-insert newly added item, but on the UI thread
string value = _class1.BindingList[e.NewIndex];
_class1.BindingList.RemoveAt(e.NewIndex);
adding = true;
_class1.BindingList.Insert(e.NewIndex, value);
adding = false;
}
}));
};
listBox1.DataSource = _class1.BindingList;
}
// ...
}
Run Code Online (Sandbox Code Playgroud)
我真的不建议这种方法。但如果你没有办法改变Class1,那就是你能做的最好的事情。