gpt4 book ai didi

c# - BindingList.Add() 即使有锁也不能跨线程工作

转载 作者:太空狗 更新时间:2023-10-30 01:15:17 25 4
gpt4 key购买 nike

刚学C#/.NET就遇到了这个问题。

所以在我的解决方案中,我有 2 个项目:winforms UI 和带有逻辑的 dll。在 dll 中,我有 BindingList,它为 UI 中的列表框提供数据源。

用户界面:

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();
}
}

逻辑类:

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);
}
}
}

所以问题是,如果我只是运行解决方案(ctrl + F5)一切正常,但在 Debug模式(F5)中按下按钮时没有任何反应。我找到的所有答案都说:“使用锁”所以我使用锁和列表框仍然没有对向列表添加元素使用react。请帮助我做错了什么或错过了什么。

PS 对不起我的英语。

最佳答案

首先要明确:您可能需要也可能不需要使用 lock这里。这将取决于实际上是否有两个或更多线程访问 BindingList<T> object concurrently,即字面上同时(例如,两个或多个线程向列表添加项目,或者一个线程添加项目,而另一个线程试图从列表中读取)。在您的代码示例中,情况似乎并非如此,因此没有必要。无论如何,lock声明所做的事情与解决您所询问的特定问题所需的事情完全不同,并且在任何情况下仅在线程使用 lock 时才有效。在同一个对象上协作(如果只有一个线程调用 lock ,那没有帮助)。


基本问题是 ListBox无法响应来自 BindingList 的事件当这些事件在 UI 线程以外的地方引发时。通常,解决这个问题的方法是调用 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));
}
}
}

然后在你的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();
}
}

这个主题的变体是在 Class1 中有两种不同的“添加”方法。 .第一个是你现在拥有的,它最终使用了一个线程。第二个是 需要 从 UI 线程调用的那个,它实际上会添加项目。在AddItem表单中的事件处理程序,而不是直接将项目添加到列表中,将调用第二个“添加”方法来为表单执行此操作。

哪个最好取决于您在 Class1 中需要多少抽象.如果您试图对其他类隐藏列表及其操作,那么变体会更好。但是,如果您不介意从 Class1 以外的其他地方更新列表代码,上面的代码示例应该没问题。

另一种方法是制作您的 Class1对象线程感知,类似于例如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);
}
}
}

在此版本中,Form1 没有变化需要。

这个基本方案有很多变体,包括一些将逻辑放入专门的 BindingList<T> 中的变体。改为子类。例如(举几个例子):
Cross-Thread Form Binding - Can it be done?
BindingList<> ListChanged event

最后,如果你真的想把东西组合在一起,你可以在列表发生变化时强制重置整个绑定(bind)。在这种情况下,您不需要更改 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;
}

// ...
}

我真的不推荐这种方法。但是如果你没有办法改变Class1 ,这是您能做的最好的事情。

关于c# - BindingList.Add() 即使有锁也不能跨线程工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39049932/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com