For中异步下载,如何控制异步的数量,还有异步操作数据库冲突了怎么处理

张天星 2018-11-11 10:38:24
求问三个问题,一个是For中异步遇到问题:
1、比如我For有1000的个循环,如何控制同时只启动20个异步,等这些异步的下载完成了,再新增一个上去?
2、此外,在For外面,如何确保For里面的所有异步全部结束,才退出For?
下面是异步操作的代码,求教:
       /// <summary>
/// 更新章节内容
/// </summary>
/// <param name="book"></param>
private async void DownloadText(BookModel book)
{
//这个是无缓存查询
var directories = directory.GetList<Model.DirectoryModel>(d => d.BookId == book.Id && d.State == true);
foreach (var directory in directories)
{
if (directory.Len == null)
{
//异步下载章节
await Task.Run(()=> { DownloadText(directory, book); });
}
}
}
/// <summary>
/// 下载章节内容
/// </summary>
/// <param name="directory"></param>
public bool DownloadText(DirectoryModel directoryModel, BookModel book)
{
DownLoadTextChanging?.Invoke(book, null);
string html = HttpHelper.DownloadHtml(directoryModel.Url);
if (html == null)
{
DownLoadTextChanged?.Invoke(book, null);
return false;
}
else
{
string bodyHtml = HtmlHelper.HtmlTrim(html, book.TextHead, book.TextTail);
HtmlToText convert = new HtmlToText();
string text = convert.Convert(bodyHtml);
//这个是有缓存查询,Ef的Find
var updateDir = directory.Find<DirectoryModel>(directoryModel.Id);
updateDir.Len = text.Length;
updateDir.Text = text;
updateDir.ActiveTime = DateTime.Now;
//更新章节
directory.Update(updateDir);
directory.SaveChanges();
DownLoadTextChanged?.Invoke(book, null);
return true;
}
}


此外就是,异步下载之后需要保存数据库,原本单线操作的时候还算正常,这次异步直接报错了:
System.InvalidOperationException:“对数据库所做的更改已成功提交,但在更新对象上下文时出错。此 ObjectContext 可能处于不一致状态。内部异常消息: 保存或接受更改失败,因为类型“FictionsDownload.Model.DirectoryModel”的多个实体具有相同的主键值。请确保显式设置的主键值是唯一的。请确保在数据库和实体框架模型中正确配置了数据库生成的主键。将 Entity Designer 用于 Database First/Model First 配置。将 "HasDatabaseGeneratedOption" Fluent API 或 "DatabaseGeneratedAttribute" 用于 Code First 配置。”

这个错误应该是我数据操作的时候实体冲突了?操作了两个相同Id的实体?
还是上面的下载代码,这其中,要保存的数据,我用Find重新取值之后,更新数据保存,每个线程的下载实体应该是不一样的,怎么会出现这中错误?
var updateDir = directory.Find<DirectoryModel>(directoryModel.Id);
updateDir.Len = text.Length;
updateDir.Text = text;
updateDir.ActiveTime = DateTime.Now;
//更新章节
directory.Update(updateDir);
directory.SaveChanges();

这个是我的项目,在Git上的链接,好心的帮忙看看,给点建议:https://gitee.com/zzwtx/FictionsDownload.git
...全文
73 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
张天星 2018-11-14
  • 打赏
  • 举报
回复
引用 8 楼 starfd 的回复:
https://blog.csdn.net/starfd/article/details/79711915
控制并发执行的Task数量


多谢,这个尝试过了,并发数限制3个,最后用Task.WaitAll等待,很好用。

而其他的回复还没有测试,明天有时间也试一下,学到一点高兴一点。。。。。。。。。。。
enaking 2018-11-13
  • 打赏
  • 举报
回复
可以用托管事件的方法通知外层。
正怒月神 2018-11-13
  • 打赏
  • 举报
回复
为什么不是并行呢。 new ParallelOptions() {MaxDegreeOfParallelism=20 } 代表线程数量
Parallel.ForEach(list, new ParallelOptions() {MaxDegreeOfParallelism=20 }, x => Console.WriteLine(x.Name));
张天星 2018-11-13
  • 打赏
  • 举报
回复
引用 3 楼 sp1234 的回复:
关于你的 ObjectContext 的并发冲突问题,这是一个基本的编程问题。怎么可能在不同线程中随便共用什么对象呢?

额,多谢,这个是我相差了,我异步方法里每次操作都重新new了一个对象,数据库更新没有问题了。
Task.WaitAll和PLinq这个,多谢指点方向,我再百度具体细节。
  • 打赏
  • 举报
回复
等待所有的都结束,可以Task.WaitAll
  • 打赏
  • 举报
回复
kusirp21 2018-11-13
  • 打赏
  • 举报
回复
想省事,TPL,Parallel可以控制线程数。想性能,Semaphore信号量控制。

TPL:
Parallel.Foreach(parallelList, parallelOptions, lambda);

性能方面
Semaphoer semaphoer = new Semaphoer(int,int);

foreach(var item in list){
// 开辟线程时申请一个资源,没有资源时会发生堵塞
semaphoer.WaitOne();
// 开辟线程开始执行
// 线程方法内部semaphoer.Release()释放一个资源。
}

// 循环结束时,还有规定个数的线程正式工作。
  • 打赏
  • 举报
回复
关于你的 ObjectContext 的并发冲突问题,这是一个基本的编程问题。怎么可能在不同线程中随便共用什么对象呢?
  • 打赏
  • 举报
回复
另外,异步并发进行处理,可以使用 PLinq 写出非常精炼的代码,不用写很繁琐的控制代码。例如
var r = (from ny in 获取可报的项目(Nian1, Yue1, Nian2, Yue2, LeiXing).AsParallel()
        from dw in 获取单位列表().AsParallel()
        let o = 申报项目(BaselienDefination, dw, ny.Item1, ny.Item2, IncludeNull, IncludeTrue, IncludeFalse, IncludeStatus0, LeiXing)
        select o).ToList();
这里的 AsParallel 不必你写 for 强10倍嘛。更何况一行 PLinq 可能代替复杂的、你需要写几百行几千行的的复杂业务逻辑。
  • 打赏
  • 举报
回复
异步的思想是(注册)回调,不是什么阻塞。for 循环所启动的任务全都完成了,可以使用 Task.WaitAll 来回调下一步的处理动做。 父线程的任务早就从这类返回了(完毕了)。不是在这里搞什么阻塞。

110,538

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧