4.0之前也没有bug。而且这也跟“加锁”扯不上关系。犯不着为了在自己家上厕所也要到工商局去登记排队(加锁)。对于多个线程共享的输入变量,你就应该“先复制引用,再使用引用副本”。就好像 int index = i;做的一样。如果说4.5之类的给你自动将参数做了栈复制和转换,那只能说它可能放弃类之前“可以改变共享变量”这个特点。丢掉了之前的特性,而默认地改变为现在的新特性。
同时访问table怎么就“没问题”?如果一个线程把table的rows给清空了,那么其它的线程线程运行时也会出错。但是这里如果不可能出现这种现象,那么就无需防止它!因此这类编程问题不是以“洁癖”为美,无法给个规定还能精当地运行,而是要随着测试的变化而重构代码的。我们其实往往需要多线程是可以高效率地修改共享变量,例如有时候我们可能用来统计运行进度(但是并不要求精确)。只不过反过来说,你也同时应该知道修改共享变量 i 的后果。
多线程代码貌似没问题,有些人看见多线程就说要加锁, 其实楼主的代码不是异步额. 问题应该处在 string str = table.Rows[i]["str"].ToString();LZ 其实是你这里跨线程了,如果没猜错,你报错的地方是string str = table.Rows[i]["str"].ToString(); 这句话,for语句完全正确,别相信上边说的什么Length-1 超出索引的错误是因为table表中一共没有10行导致或者说没有列名叫str的 你这样写而且不安全,因为跨线程,多个线程同时访问一个table没问题,但是同时访问变化的 i 变量会有问题的 比如说,循环第一次,当线程开启的时候 循环第二次(此时已经执行i++) 当执行string str = table.Rows[i]["str"].ToString();这时候i=1,取到的是第二行,或者说当循环都循环到第10次了,第一次的线程才开始执行,那i就是9了,完全不对 还有一个问题,你一直在new 线程,并没有释放,线程多了 卡爆你 31楼的朋友,这里没有涉及线程同步哦, t1[i].Start(); Thread.Sleep(1000); 线程启动是在主线程里,但是 Thread.Sleep(1000);也阻塞的是主线程, Thread[] t1 = new Thread[10]; for (int i = 0; i < t1.Length; i++) { t1[i] = new Thread(() => { Console.WriteLine("the i is " + i+",thread is :"+Thread.CurrentThread.Name); }) { Name = "thread"+i };
t1[i].Start(); Thread.Sleep(1000); }结果: the i is 0,thread is :thread0 the i is 1,thread is :thread1 the i is 2,thread is :thread2 the i is 3,thread is :thread3 the i is 4,thread is :thread4 the i is 5,thread is :thread5 the i is 6,thread is :thread6 the i is 7,thread is :thread7 the i is 8,thread is :thread8 the i is 9,thread is :thread9 请按任意键继续. . .
LZ 其实是你这里跨线程了,如果没猜错,你报错的地方是string str = table.Rows[i]["str"].ToString(); 这句话,for语句完全正确,别相信上边说的什么Length-1 超出索引的错误是因为table表中一共没有10行导致或者说没有列名叫str的 你这样写而且不安全,因为跨线程,多个线程同时访问一个table没问题,但是同时访问变化的 i 变量会有问题的 比如说,循环第一次,当线程开启的时候 循环第二次(此时已经执行i++) 当执行string str = table.Rows[i]["str"].ToString();这时候i=1,取到的是第二行,或者说当循环都循环到第10次了,第一次的线程才开始执行,那i就是9了,完全不对 还有一个问题,你一直在new 线程,并没有释放,线程多了 卡爆你 31楼的朋友,这里没有涉及线程同步哦, t1[i].Start(); Thread.Sleep(1000); 线程启动是在主线程里,但是 Thread.Sleep(1000);也阻塞的是主线程, Thread[] t1 = new Thread[10]; for (int i = 0; i < t1.Length; i++) { t1[i] = new Thread(() => { Console.WriteLine("the i is " + i+",thread is :"+Thread.CurrentThread.Name); }) { Name = "thread"+i };
t1[i].Start(); Thread.Sleep(1000); }结果: the i is 0,thread is :thread0 the i is 1,thread is :thread1 the i is 2,thread is :thread2 the i is 3,thread is :thread3 the i is 4,thread is :thread4 the i is 5,thread is :thread5 the i is 6,thread is :thread6 the i is 7,thread is :thread7 the i is 8,thread is :thread8 the i is 9,thread is :thread9 请按任意键继续. . . 怎么说呢,只是他写了睡眠而已,1000ms保证了子线程代码执行完成
同时访问table怎么就“没问题”?如果一个线程把table的rows给清空了,那么其它的线程线程运行时也会出错。但是这里如果不可能出现这种现象,那么就无需防止它!因此这类编程问题不是以“洁癖”为美,无法给个规定还能精当地运行,而是要随着测试的变化而重构代码的。我们其实往往需要多线程是可以高效率地修改共享变量,例如有时候我们可能用来统计运行进度(但是并不要求精确)。只不过反过来说,你也同时应该知道修改共享变量 i 的后果。 我的意思是说同时访问table是读,不是改
没有问题
table.Rows[i]只是个例子演示给你们看而已,肯定不会错的断点调试过了,就是因为for循环的i+1后,前面代码里的i也会跟着改变了
Thread[] thread = new Thread[dataGridViewX1.Rows.Count];
for (int i = 0; i < dataGridViewX1.Rows.Count; i++)
{
thread[i] = new Thread(new ThreadStart(delegate
{
DataTable tableWeb = SqliteHelper.GetWeblist(dataGridViewX1.Rows[i].Cells[1].Value.ToString());
DataTable tableCollect = new DataTable();
DataTable tableKeyword = SqliteHelper.GetKeyword(dataGridViewX1.Rows[i].Cells[5].Value.ToString());
DataTable tableArticle;
CollectRule rule = new CollectRule();
KeyCollect keyMethod = new KeyCollect();
ArrayList arrayList = new ArrayList();
Encoding enc = null;
//调试的时候这里的i也会报错
if (dataGridViewX1.Rows[i].Cells[2].Value.ToString().Contains("关键"))
{
tableCollect = SqliteHelper.GetCollectRule("name", dataGridViewX1.Rows[i].Cells[2].Value.ToString());
if (tableCollect.Rows[0]["encoding"].ToString().ToLower() == "gb2312")
{
enc = Encoding.GetEncoding("gb2312");
}
else if (tableCollect.Rows[0]["encoding"].ToString().ToLower() == "utf8")
{
enc = Encoding.UTF8;
}
arrayList = keyMethod.GetListUrl(tableCollect.Rows[0]["listUrl"].ToString(), tableKeyword.Rows[0]["value"].ToString(), tableCollect.Rows[0]["listTagStart"].ToString(), tableCollect.Rows[0]["listTagEnd"].ToString(), tableCollect.Rows[0]["suffix"].ToString(), 5, Convert.ToInt32(tableCollect.Rows[0]["cdouble"].ToString()), enc);
}
else if (dataGridViewX1.Rows[i].Cells[2].Value.ToString().Contains("自定义"))
{
tableCollect = SqliteHelper.GetCollectRule("name", dataGridViewX1.Rows[i].Cells[2].Value.ToString());
if (tableCollect.Rows[0]["encoding"].ToString().ToLower() == "gb2312")
{
enc = Encoding.GetEncoding("gb2312");
}
else if (tableCollect.Rows[0]["encoding"].ToString().ToLower() == "utf8")
{
enc = Encoding.UTF8;
}
arrayList = rule.GetListUrl(tableCollect.Rows[0]["listUrl"].ToString(), 22, tableCollect.Rows[0]["listTagStart"].ToString(), tableCollect.Rows[0]["listTagEnd"].ToString(), tableCollect.Rows[0]["suffix"].ToString(), Convert.ToInt32(tableCollect.Rows[0]["cdouble"].ToString()), enc);
}
for (int j = 0; j < arrayList.Count; j++)
{
string title = string.Empty;
string content = rule.GetContent(arrayList[j].ToString(), tableCollect.Rows[0]["contentTagStart"].ToString(), tableCollect.Rows[0]["contentTagEnd"].ToString(), tableCollect.Rows[0]["titleTagStart"].ToString(), tableCollect.Rows[0]["titleTagEnd"].ToString(), out title, enc);
if (SqliteHelper.AddArticle(title, content, dataGridViewX1.Rows[0].Cells[4].Value.ToString()) == 1)
{
Cmd.Print("采集到1条标题为:《" + title + "》的数据",ConsoleColor.Green);
}
else
{
Cmd.Print("采集入库时失败标题为:《" + title + "》的数据",ConsoleColor.Red);
}
}
//这里的i会报错 索引超出.....
tableArticle = SqliteHelper.GetArticle(dataGridViewX1.Rows[i].Cells[4].Value.ToString());
Cmd.Print("采集完成,正在发布", ConsoleColor.Yellow);
for (int j = 0; j < tableArticle.Rows.Count; j++)
{
PublishArticle(tableWeb.Rows[0]["cms"].ToString(), tableWeb.Rows[0]["url"].ToString(), tableWeb.Rows[0]["user"].ToString(), tableWeb.Rows[0]["pass"].ToString(),tableArticle.Rows[j]["title"].ToString(),tableArticle.Rows[j]["content"].ToString());
}
}));
thread[i].Start();
Thread.Sleep(1000);
}
只能到9,不会出现10 的
i=10的时候,不是直接就走出去了么,为什么一部分代码走,另一部分代码不走?
不会有这个情况的,i是值类型的
我没说i不是值类型啊,但问题他没有把i作为参数传入线程,而是把i作为一个全局变量在使用,for循环停止的时候i=10,这时候不管你哪个线程,只要没有走完i都是10
不对的,i 的变化不会影响已经开始的线程
但奇怪的是 i的变化的确影响了已经开始了的线程
并在匿名委托方法体中用委托参数代替i
用thread[i].Start(i)来启动线程
是的
但我写成把i作为方法的参数传递过去,还是会报这种索引的错,
private DataTable TableArticle(int i)
{
return SqliteHelper.GetArticle(dataGridViewX1.Rows[i].Cells[4].Value.ToString());
}
tableArticle = TableArticle(i);
Thread[] thread = new Thread[dataGridViewX1.Rows.Count];
for (int i = 0; i < dataGridViewX1.Rows.Count; i++)
{
thread[i] = new Thread(new ParameterizedThreadStart(DoYourWork));
thread[i].Start(i);
Thread.Sleep(1000);
}public void DoYourWork(object index)
{
//你的代码
}
Thread[] thread = new Thread[dataGridViewX1.Rows.Count];
for (int i = 0; i < dataGridViewX1.Rows.Count; i++)
{
//以前一直用new Thread(new ThreadStart(delegate{} 这种写法
thread[i] = new Thread(new ThreadStart(delegate
{
ThreadMethod(i);
}));
thread[i].Start();
Thread.Sleep(1000);
}
for (int i = 0; i < dataGridViewX1.Rows.Count; i++)
{
//现在用new ParameterizedThreadStart(ThreadMethod),不知这两个有什么区别,利弊?感觉效果都一样
thread[i] = new Thread(new ParameterizedThreadStart(ThreadMethod));
thread[i].Start(i);
Thread.Sleep(1000);
}
for (int i = 0; i < t1.Length; i++)
{
t1[i] = new Thread(new ThreadStart(delegate(object state)
{
//
//Do something......
//Do something......
//Do something......
//
//当循环到最后一次的时候,i会变成10,那for循环就肯定不会再执行下去,但这里的i也会变成10,就会报索引超出数组界限的错误。
//而且在for循环里面的所有的i都会报这个错误。
int index = (int)state;
string str = table.Rows[index]["str"].ToString();
}
));
t1[i].Start(i);
Thread.Sleep(1000);
}原因是线程启动需要时间,而for循环执行去很快,这样就会导致线程里的代码执行的时候,外部变量i早已被for改掉了
你可以使用传参的重载,将每次循环的i值(object类型,可以传任何数据)传到线程要执行的委托
超出索引的错误是因为table表中一共没有10行导致或者说没有列名叫str的
你这样写而且不安全,因为跨线程,多个线程同时访问一个table没问题,但是同时访问变化的 i 变量会有问题的
比如说,循环第一次,当线程开启的时候 循环第二次(此时已经执行i++) 当执行string str = table.Rows[i]["str"].ToString();这时候i=1,取到的是第二行,或者说当循环都循环到第10次了,第一次的线程才开始执行,那i就是9了,完全不对
还有一个问题,你一直在new 线程,并没有释放,线程多了 卡爆你
之前的版本可以使用。
for (int i = 0; i < t1.Length; i++)
{
int index = i;
t1[i] = new Thread(new ThreadStart(delegate
{
//
//Do something......
//Do something......
//Do something......
//
//当循环到最后一次的时候,i会变成10,那for循环就肯定不会再执行下去,但这里的i也会变成10,就会报索引超出数组界限的错误。
//而且在for循环里面的所有的i都会报这个错误。
string str = table.Rows[index]["str"].ToString();
}
));
t1[i].Start();
Thread.Sleep(1000);
}
也就是说这是4.0的BUG?,4.5 就不会因为全局变量改变而影响其他, string str = table.Rows[index]["str"].ToString(); 像这个4.5把他独立分了一个包去执行,不受其他的干扰,是这样么
同时访问table怎么就“没问题”?如果一个线程把table的rows给清空了,那么其它的线程线程运行时也会出错。但是这里如果不可能出现这种现象,那么就无需防止它!因此这类编程问题不是以“洁癖”为美,无法给个规定还能精当地运行,而是要随着测试的变化而重构代码的。我们其实往往需要多线程是可以高效率地修改共享变量,例如有时候我们可能用来统计运行进度(但是并不要求精确)。只不过反过来说,你也同时应该知道修改共享变量 i 的后果。
其实楼主的代码不是异步额.
问题应该处在
string str = table.Rows[i]["str"].ToString();LZ 其实是你这里跨线程了,如果没猜错,你报错的地方是string str = table.Rows[i]["str"].ToString(); 这句话,for语句完全正确,别相信上边说的什么Length-1
超出索引的错误是因为table表中一共没有10行导致或者说没有列名叫str的
你这样写而且不安全,因为跨线程,多个线程同时访问一个table没问题,但是同时访问变化的 i 变量会有问题的
比如说,循环第一次,当线程开启的时候 循环第二次(此时已经执行i++) 当执行string str = table.Rows[i]["str"].ToString();这时候i=1,取到的是第二行,或者说当循环都循环到第10次了,第一次的线程才开始执行,那i就是9了,完全不对
还有一个问题,你一直在new 线程,并没有释放,线程多了 卡爆你
31楼的朋友,这里没有涉及线程同步哦,
t1[i].Start();
Thread.Sleep(1000);
线程启动是在主线程里,但是 Thread.Sleep(1000);也阻塞的是主线程,
Thread[] t1 = new Thread[10]; for (int i = 0; i < t1.Length; i++)
{
t1[i] = new Thread(() =>
{
Console.WriteLine("the i is " + i+",thread is :"+Thread.CurrentThread.Name);
})
{
Name = "thread"+i
};
t1[i].Start();
Thread.Sleep(1000);
}结果:
the i is 0,thread is :thread0
the i is 1,thread is :thread1
the i is 2,thread is :thread2
the i is 3,thread is :thread3
the i is 4,thread is :thread4
the i is 5,thread is :thread5
the i is 6,thread is :thread6
the i is 7,thread is :thread7
the i is 8,thread is :thread8
the i is 9,thread is :thread9
请按任意键继续. . .
也就是说这是4.0的BUG?,4.5 就不会因为全局变量改变而影响其他, string str = table.Rows[index]["str"].ToString(); 像这个4.5把他独立分了一个包去执行,不受其他的干扰,是这样么
这不是bug,而是一种设计方式,左手右手的选择而已,4.5更改了设计。
超出索引的错误是因为table表中一共没有10行导致或者说没有列名叫str的
你这样写而且不安全,因为跨线程,多个线程同时访问一个table没问题,但是同时访问变化的 i 变量会有问题的
比如说,循环第一次,当线程开启的时候 循环第二次(此时已经执行i++) 当执行string str = table.Rows[i]["str"].ToString();这时候i=1,取到的是第二行,或者说当循环都循环到第10次了,第一次的线程才开始执行,那i就是9了,完全不对
还有一个问题,你一直在new 线程,并没有释放,线程多了 卡爆你
31楼的朋友,这里没有涉及线程同步哦,
t1[i].Start();
Thread.Sleep(1000);
线程启动是在主线程里,但是 Thread.Sleep(1000);也阻塞的是主线程,
Thread[] t1 = new Thread[10]; for (int i = 0; i < t1.Length; i++)
{
t1[i] = new Thread(() =>
{
Console.WriteLine("the i is " + i+",thread is :"+Thread.CurrentThread.Name);
})
{
Name = "thread"+i
};
t1[i].Start();
Thread.Sleep(1000);
}结果:
the i is 0,thread is :thread0
the i is 1,thread is :thread1
the i is 2,thread is :thread2
the i is 3,thread is :thread3
the i is 4,thread is :thread4
the i is 5,thread is :thread5
the i is 6,thread is :thread6
the i is 7,thread is :thread7
the i is 8,thread is :thread8
the i is 9,thread is :thread9
请按任意键继续. . .
怎么说呢,只是他写了睡眠而已,1000ms保证了子线程代码执行完成
同时访问table怎么就“没问题”?如果一个线程把table的rows给清空了,那么其它的线程线程运行时也会出错。但是这里如果不可能出现这种现象,那么就无需防止它!因此这类编程问题不是以“洁癖”为美,无法给个规定还能精当地运行,而是要随着测试的变化而重构代码的。我们其实往往需要多线程是可以高效率地修改共享变量,例如有时候我们可能用来统计运行进度(但是并不要求精确)。只不过反过来说,你也同时应该知道修改共享变量 i 的后果。
我的意思是说同时访问table是读,不是改
而且你发现了没有你用的多线程,而且你委托套在循环里,你以为没启动委托,但是因为循环太快,你i=10的时候,最后一个委托还没托出去,就把值i变成了10,所以就产生了你所谓的错误,i变成了10,其实你委托完全可以写到外面去啊,直接赋值多好?