使用TLSharp進行Telegram中遭遇迴圈體內報session.dat檔案被佔用時解決方式一例

布洛陀發表於2020-10-22

背景

公司做Telegram開發,.net Framework專案,呼叫TLSharp作為框架進行開發。
開發需求是讀取群裡新到達的資訊並進行過濾。
由此不可避免得要用到

TLSharp.Core.TelegramClient.GetHistoryAsync(TLAbsInputPeer peer, int offsetId = 0, int offsetDate = 0, int addOffset = 0, int limit = 100, int maxId = 0, int minId = 0, CancellationToken token = default);

這一方法。


由於每次都只能取得一個群的聊天曆史記錄,顯然地在讀取群列表之後第一想到地就是用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的訪問有要求,而迴圈上一條還沒有結束,下一條就已經要訪問。
為了驗證這一點,手工用斷點停幾秒再執行,發現不報錯了!
這就更能說明問題了:

TLSharp中的方法使用了多執行緒對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 =>
                {
                    //實際處理訊息
                });
            }

相關文章