前言
在年初時,因為 Bot 送給 IM Channel 時,需要在 HttpClient 的 Headers 中加入額外的資訊,那時筆者是透過 Middleware 的方式,取出 turnContext.TurnState 中的 ConnectorClient 。然後設定 ConnectorClient.HttpClient 的 DefaultRequestHeaders 。詳細可以參考 Microsoft Botframework V4,從 Bot 送訊息到 IM Channel 時,加入額外的 Header 資訊
最近 Bot 有時會噴以下的錯誤,
**
System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection’s state is no longer correct.
at System.Collections.Generic.Dictionary2.FindEntry(TKey key) at System.Collections.Generic.Dictionary2.TryGetValue(TKey key, TValue& value) at System.Net.Http.Headers.HttpHeaders.ContainsParsedValue(HeaderDescriptor descriptor, Object value) at System.Net.Http.Headers.HttpHeaderValueCollection1.get_IsSpecialValueSet() at System.Net.Http.Headers.HttpRequestHeaders.get_ExpectContinue() at ** 或是 ** System.NullReferenceException: Object reference not set to an instance of an object. at System.Collections.Generic.Dictionary\
2.FindEntry(TKey key) at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at System.Net.Http.Headers.HttpHeaders.GetOrCreateHeaderInfo(HeaderDescriptor descriptor, Boolean parseRawValues)
at System.Net.Http.headers.HttpHeaders.TryAddWithoutValidation(HeaderDescriptor descriptor, String value)
**
研究
網路上討論,大約是因為 Multi Thread 去 Access Dictionary 物件所引發的錯誤。
因為 System.Collections.Generic.Dictionary 它並不是 Thread Safe 的物件,所以在 Multi Threads 下,有可能會造成錯誤。
所以回過頭來看一下, 原本 Middleware 的部份,它是在系統一啟動時,就建立的,所以在多人使用時,的確會在 Multi Threads 的環境下,
1 | services.AddBot<EmptyBotBot>(options => |
所以,我們不能在 Middleware 中去操作 ConnectorClient.HttpClient 的 Headers ,除非加上 Lock 。
那 ConnectorClient.HttpClient 是從那裡來的呢?
其實它是取自 BotFrameworkAdapter 中的 _httpClient ,在 BotFrameworkAdapter 的 CreateConnectorClient Method 建立 ConnectorClient 時,將 _httpClient 給進 ConnectorClient 的建構子,詳細可以參考 Git BotFrameworkAdapter.cs
那 BotFrameworkAdapter 中的 _httpClient 又是從那裡來的呢? 如果沒有設定的話,它就拿 _defaultHttpClient (private static readonly HttpClient _defaultHttpClient = new HttpClient();) 來用。
那我們要如何建立 HttpClient 給 BotFrameworkAdapter 用呢? 其實它是取自 BotframeworkOptions 的 HttpClient 的屬性值,詳細可以參考 Git ServiceCollectionExtensions.cs
解法
所以,原本為了加 Header 的 Middleware 可以不需要了(不然在操作時,需要 lock ),直接建立帶有我們需要 Header 的 HttpClient 物件就可以了。
所以程式調整如下,
1 | services.AddBot<EmptyBotBot>(options => |
透過 ngrok 來看 Bot 送到 IM Channel (POST /v3/conversations/…) 都會有我們需要的 Header 。
參考資料
Microsoft Botframework V4,從 Bot 送訊息到 IM Channel 時,加入額外的 Header 資訊