背景

公司做Telegram开发,.net Framework项目,调用TLSharp作为框架进行开发。
开发需求是读取群里新到达的信息并进行过滤。
由此不可避免得要用到

这一方法。


由于每次都只能取得一个群的聊天历史记录,显然地在读取群列表之后第一想到地就是用linq

(await Listener.Client.SendRequestAsync<TLChats>(new TLRequestGetAllChats()
            { ExceptIds = new TeleSharp.TL.TLVector<int>() })
                .ConfigureAwait(false))
                .Chats
                .Where(item => item.GetType() == typeof(TLChannel))
                .Cast<TLChannel>()
                .ToList()
                .ForEach(async item =>
                {
                    ((TLChannelMessages)await Listener.Client.GetHistoryAsync(
                        peer: new TLInputPeerChannel()
                        {
                            ChannelId = item.Id,
                            AccessHash = item.AccessHash.Value
                        }))
                        .Messages
                        .Where(subitem =>
                            subitem.GetType() == typeof(TLMessage))
                            .Cast<TLMessage>()
                            .Where(subitem =>
                                (subitem.Entities == null
                                || (subitem.Entities != null && subitem.Entities.Count() < 5))
                                && !string.IsNullOrWhiteSpace(subitem.Message))
                            .ToList()
                            .ForEach(subitem =>
                            {
                                //实际处理消息
                            });

                });

但是很意外的,跑挂了!

报出的原因是session.dat文件被占用。

探索

session.dat文件是TG的消息会话文件,受TLSharp管控,因此不能自主去管理文件的打开关闭和释放。
于是抱着试一试的心理,把异步去掉了,再跑起来,还是一样的错误。
难道是Linq的问题?还是因为没有加ConfigAwait(false)?
这个框架下试了几次,均报session.dat被占用。

于是用foreach改写了这一段:

List<TLChannel> AllGroups = (await Listener.Client.SendRequestAsync<TLChats>(new TLRequestGetAllChats()
            { ExceptIds = new TeleSharp.TL.TLVector<int>() })
                 .ConfigureAwait(false))
                 .Chats
                 .Where(item => item.GetType() == typeof(TLChannel))
                 .Cast<TLChannel>()
                 .ToList();

            foreach (TLChannel item in AllGroups)
            {
                ((TLChannelMessages)await Listener.Client.GetHistoryAsync(
                    peer: new TLInputPeerChannel()
                    {
                        ChannelId = item.Id,
                        AccessHash = item.AccessHash.Value
                    }))
                    .Messages
                    .Where(subitem =>
                        subitem.GetType() == typeof(TLMessage))
                        .Cast<TLMessage>()
                        .Where(subitem =>
                            (subitem.Entities == null
                            || (subitem.Entities != null && subitem.Entities.Count() < 5))
                            && !string.IsNullOrWhiteSpace(subitem.Message))
                        .ToList()
                        .ForEach(subitem =>
                        {
                                //实际处理消息
                        });

            };

继续跑,继续挂!!


然后其实又把foreach改成了for(;😉,问题依旧!!!

解决

拆到for循环之后,因为方便断点了,发现每次出问题都不是在第一个数据,很大概率也不是发生在第二个数据,一般都是第三个才开始报占用错误,
这就带来了思考的空间。
很显然是循环体内的方法对session.dat的访问有要求,而循环上一条还没有结束,下一条就已经要访问。
为了验证这一点,手工用断点停几秒再执行,发现不报错了!
这就更能说明问题了:

为了验证这个想法并使代码能跑起来,我把代码段复制了六个,接收的变量也改用数组,发现能跑了。
于是最终把阶段结果改用数组存储,成功解决问题:

List<TLChannel> AllGroups = (await Listener.Client.SendRequestAsync<TLChats>(new TLRequestGetAllChats() { ExceptIds = new TeleSharp.TL.TLVector<int>() }).ConfigureAwait(false)).Chats.Where(item => item.GetType() == typeof(TLChannel)).Cast<TLChannel>().ToList();

            TLChannelMessages[] MessagesArray = new TLChannelMessages[AllGroups.Count];
            for (int i = 0; i < AllGroups.Count; i++)
            {
                MessagesArray[i] = (TLChannelMessages)await Listener.Client.GetHistoryAsync(peer: new TLInputPeerChannel() { ChannelId = AllGroups[i].Id, AccessHash = AllGroups[i].AccessHash.Value });
                MessagesArray[i].Messages.Where(item => item.GetType() == typeof(TLMessage)).Cast<TLMessage>().Where(item => (item.Entities == null || (item.Entities != null && item.Entities.Count() < 5)) && !string.IsNullOrWhiteSpace(item.Message)).ToList().ForEach(item =>
                {
                    //实际处理消息
                });
            }
10-23 10:02