Fak*_*hid 4 c# foreach for-loop exception
我正在使用foreach循环来处理要处理的数据列表(一旦处理就删除了所述数据 - 这是在锁内).此方法偶尔会导致ArgumentException.
抓住它本来是昂贵的,所以我试图追查这个问题,但我无法弄明白.
我已经切换到for循环,问题似乎已经消失了.有人能解释发生了什么吗?即使有异常消息,我也不太了解幕后发生了什么.
为什么for循环显然有效?我是否设置了foreach循环错误或什么?
这几乎是我的循环设置方式:
foreach (string data in new List<string>(Foo.Requests))
{
// Process the data.
lock (Foo.Requests)
{
Foo.Requests.Remove(data);
}
}
Run Code Online (Sandbox Code Playgroud)
和
for (int i = 0; i < Foo.Requests.Count; i++)
{
string data = Foo.Requests[i];
// Process the data.
lock (Foo.Requests)
{
Foo.Requests.Remove(data);
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:for*循环是这样的设置,如下所示:
while (running)
{
// [...]
}
Run Code Online (Sandbox Code Playgroud)
编辑:根据要求添加了有关异常的更多信息.
System.ArgumentException: Destination array was not long enough. Check destIndex and length, and the array's lower bounds
at System.Array.Copy (System.Array sourceArray, Int32 sourceIndex, System.Array destinationArray, Int32 destinationIndex, Int32 length) [0x00000]
at System.Collections.Generic.List`1[System.String].CopyTo (System.String[] array, Int32 arrayIndex) [0x00000]
at System.Collections.Generic.List`1[System.String].AddCollection (ICollection`1 collection) [0x00000]
at System.Collections.Generic.List`1[System.String]..ctor (IEnumerable`1 collection) [0x00000]
Run Code Online (Sandbox Code Playgroud)
编辑:锁定的原因是有另一个线程添加数据.此外,最终,多个线程将处理数据(因此,如果整个设置错误,请告知).
编辑:很难找到一个好的答案.
我发现埃里克·利珀特的评论值得,但他并没有真正回答(无论如何都投了他的评论).
Pavel Minaev,Joel Coehoorn和Thorarin都给出了我喜欢和投票的答案.索拉林还花了20分钟写了一些有用的代码.
我可以接受所有这三个并让它分享声誉但唉.
Pavel Minaev是下一个应得的人,所以他获得了荣誉.
感谢帮助好人.:)
Pav*_*aev 13
你的问题是,它的构造函数(List<T>从IEnumerable你调用的内容)创建一个新列表对于它的参数是不是线程安全的.会发生什么事情,虽然这个:
new List<string>(Foo.Requests)
Run Code Online (Sandbox Code Playgroud)
在执行时,另一个线程改变Foo.Requests.你必须在通话期间锁定它.
正如埃里克所指出的那样,另一个问题List<T>并不能保证读者在另一个线程正在改变它时更安全.即并发读者是好的,但并发读者和作者不是.当您将写入锁定为彼此时,您不会将读取锁定在写入上.
您的锁定方案已被破坏.您需要锁定Foo.Requests()循环的整个持续时间,而不仅仅是在删除项目时.否则,在"处理数据"操作过程中,该项可能会变为无效,并且枚举可能会在从一个项目移动到另一个项目之间发生变化.这假设您不需要在此间隔期间插入集合.如果是这种情况,您确实需要重新考虑使用适当的生产者/消费者队列.
看到你的例外后; 它看起来我正在构建浅拷贝时改变Foo.Requests.将其更改为以下内容:
List<string> requests;
lock (Foo.Requests)
{
requests = new List<string>(Foo.Requests);
}
foreach (string data in requests)
{
// Process the data.
lock (Foo.Requests)
{
Foo.Requests.Remove(data);
}
}
Run Code Online (Sandbox Code Playgroud)
话虽如此,我有点怀疑上面是你想要的.如果在处理期间有新请求进入,则当foreach循环终止时,它们将不会被处理.由于我很无聊,这里有一些我认为你想要实现的东西:
class RequestProcessingThread
{
// Used to signal this thread when there is new work to be done
private AutoResetEvent _processingNeeded = new AutoResetEvent(true);
// Used for request to terminate processing
private ManualResetEvent _stopProcessing = new ManualResetEvent(false);
// Signalled when thread has stopped processing
private AutoResetEvent _processingStopped = new AutoResetEvent(false);
/// <summary>
/// Called to start processing
/// </summary>
public void Start()
{
_stopProcessing.Reset();
Thread thread = new Thread(ProcessRequests);
thread.Start();
}
/// <summary>
/// Called to request a graceful shutdown of the processing thread
/// </summary>
public void Stop()
{
_stopProcessing.Set();
// Optionally wait for thread to terminate here
_processingStopped.WaitOne();
}
/// <summary>
/// This method does the actual work
/// </summary>
private void ProcessRequests()
{
WaitHandle[] waitHandles = new WaitHandle[] { _processingNeeded, _stopProcessing };
Foo.RequestAdded += OnRequestAdded;
while (true)
{
while (Foo.Requests.Count > 0)
{
string request;
lock (Foo.Requests)
{
request = Foo.Requests.Peek();
}
// Process request
Debug.WriteLine(request);
lock (Foo.Requests)
{
Foo.Requests.Dequeue();
}
}
if (WaitHandle.WaitAny(waitHandles) == 1)
{
// _stopProcessing was signalled, exit the loop
break;
}
}
Foo.RequestAdded -= ProcessRequests;
_processingStopped.Set();
}
/// <summary>
/// This method will be called when a new requests gets added to the queue
/// </summary>
private void OnRequestAdded()
{
_processingNeeded.Set();
}
}
static class Foo
{
public delegate void RequestAddedHandler();
public static event RequestAddedHandler RequestAdded;
static Foo()
{
Requests = new Queue<string>();
}
public static Queue<string> Requests
{
get;
private set;
}
public static void AddRequest(string request)
{
lock (Requests)
{
Requests.Enqueue(request);
}
if (RequestAdded != null)
{
RequestAdded();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这还有一些问题,我将留给读者: