【问题标题】:Threading error while deserializing JSON反序列化 JSON 时出现线程错误
【发布时间】:2015-10-13 02:16:48
【问题描述】:

所以我为我的客户制作了一个请求包装器,一切正常。但是突然(我不知道为什么)JsonConvert.DeserializeObject<T>(c) 抛出了经典异常

调用线程无法访问此对象,因为不同的线程拥有它

嗯,除了这个,我没有看到任何其他线程。它们都是局部变量,根据 Newtonsoft https://github.com/JamesNK/Newtonsoft.Json/issues/469

每次反序列化对象时都会创建一个新的 JsonSerializerInternalReader

你知道这个异常所说的另一个线程在哪里吗?

    public static Task<Response<T>> _reqWrapper<T>(Func<Task<HttpResponseMessage>> request) 
        where T : class
    {
        return Task.Run(async () =>
        {
            var response = new Response<T>();

            var hrm = await request().ConfigureAwait(false);                              
            var c = await hrm.Content.ReadAsStringAsync().ConfigureAwait(false);
            response.Content = JsonConvert.DeserializeObject<T>(c);

            return response;
        });

已经尝试过,但运气不佳。

response.Content = await Task.Run(() => JsonConvert.DeserializeObject<T>(c));

更新

为了确保那条线是我做的那条线:

T t = null;
try
{
    t = JsonConvert.DeserializeObject<T>(c);
}
catch { }
response.Content = t

一切都运行良好。有什么线索吗?

更新 2

堆栈跟踪

我在这里看到的是序列化程序正在尝试访问主窗口。我不得不说这是在ShowDialog() 窗口内发生的,所以我猜主窗口不可用。但我不确定我是否正确或如何解决此问题。

在 System.Windows.Threading.Dispatcher.VerifyAccess() 在 System.Windows.Application.get_MainWindow() 在 C:... 中的 ControliWindows.Globals.Controli.get_Window() 在 ControliWindows.Globals.Framework.Modalizer.SaveableModel1..ctor() 在 C:... 在 C:... 中的 ControliWindows.Views.Modals.AccountMm..ctor() 在 CreateControliWindows.Views.Modals.AccountMm() 在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader 阅读器,JsonObjectContract objectContract,JsonProperty containerMember,JsonProperty containerProperty,String id,Boolean& createdFromNonDefaultCreator) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader 阅读器,类型 objectType,JsonContract 合同,JsonProperty 成员,JsonContainerContract containerContract,JsonProperty containerMember,对象现有值) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader 阅读器,类型 objectType,JsonContract 合同,JsonProperty 成员,JsonContainerContract containerContract,JsonProperty containerMember,对象现有值) 在 Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader 阅读器,类型 objectType,布尔 checkAdditionalContent) 在 Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader 阅读器,类型 objectType) 在 Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader 阅读器,类型 objectType) 在 Newtonsoft.Json.JsonConvert.DeserializeObject(字符串值,类型类型,JsonSerializerSettings 设置) 在 Newtonsoft.Json.JsonConvert.DeserializeObject[T](字符串值,JsonSerializerSettings 设置) 在 Newtonsoft.Json.JsonConvert.DeserializeObject[T](字符串值) 在 ControliWindows.Globals.Connection.c__DisplayClass39_0`1.b__0>d.MoveNext() in C:...

【问题讨论】:

  • 一旦你等待某事,一旦你恢复,你很可能在不同的线程上运行。不知道为什么在这种情况下这很重要。
  • 试试不带ConfigureAwait(false)
  • @bto.rdz 请发布您的整个代码,因为它几乎肯定与您上面的 sn-p 无关。在某个地方,会有一段代码与 UI 组件交互。至少,您的PostAsync 上发生了异常,这意味着您发布的响应代码甚至还没有执行。
  • @EricJ。通常await 之后的代码将在 WinForms/WPF/ASP.Net 的原始线程上运行...实际上在 OP 的代码中并非如此,因为它们明确要求不要回到原始线程(两次)-一次使用 @987654330 @ 并且绝对确定 ConfigureAwait(false)...
  • 我认为Task.Run 等从这里的图片中消失 - 只是等待事情而不使用 ConfigureAwait(false)。那时您不会切换到其他逻辑调用上下文,也许一切都会好起来的。如果您确实需要启动一个单独的线程,从而让事情真正并行运行(而不是允许通过 async/await 进行重叠响应),请轻轻引入它,看看是什么导致您的失败返回。

标签: c# multithreading json.net async-await httpclient


【解决方案1】:

在 System.Windows.Threading.Dispatcher.VerifyAccess() 在 System.Windows.Application.get_MainWindow() 在 ControliWindows.Globals.Controli.get_Window() 在 C:... 在 ControliWindows.Globals.Framework.Modalizer。 SaveableModel1..ctor() in C:... at ControliWindows.Views.Modals.AccountMm..ctor() in C:... at

这就是你问题的根源。以下是正在发生的一系列事件:

  • 您的TControliWindows.Views.Modals.AccountMmDeserializeObject 必须创建一个新的
  • AccountMm 的构造函数它正在创建一个 ControliWindows.Globals.Framework.Modalizer.SaveableModel1
  • SaveableModel1 的构造函数正在读取属性ControliWindows.Globals.Controli.Window
  • Controli.Window 中,它正在读取System.Windows.Application.Window 的属性
  • Application.Window 只能从 UI 线程读取,这整个事件链发生在 Threadpool 线程上并导致您的异常。

最简单的解决方案是让ControliWindows.Globals.Controli.Window 检测它是否不在 UI 线程上,以及是否没有调用 UI 以获取 Application.Window 的值。

public static class Controli
{
    public Window Window
    {
        get
        {
            var application = Application.Current;
            if(application == null)
                return null;
            try
            {
                return application.MainWindow;
            }
            catch(InvalidOperationException)
            {
                return application.Dispatcher.Invoke(() => application.MainWindow);
            }
        }
    }
}

【讨论】:

  • 我很惊讶你只用堆栈跟踪描述了我的代码,我希望我能读懂所有这些。你说的很有道理,我会试试的。谢谢
  • @bto.rdz 我更新了一个如何调用的示例,如果需要的话,这段代码是写在网站上的,所以可能有一两个错误。
  • @bto.rdz 看看在查看完整的异常信息时诊断此类问题有多容易?
  • @usr 感谢您的提示,我想我需要练习阅读堆栈跟踪。这对我来说并没有太大意义,但一切都在
  • @bto.rdz 以后在调试器中使用Copy Exception Details to Clipboard 链接,然后将文本粘贴到您的问题中的&lt;pre&gt;&lt;/pre&gt; 标记中(这些标记有助于保持文本的格式)。只需单击一下即可为您提供异常名称、消息和堆栈跟踪(它正在处理您的异常的 .ToString() 的输出,当您要记录异常时,请使用 .ToString() 在你的代码)
【解决方案2】:

通过使用ConfigureAwait(false),您明确告诉您的等待在执行等待后不要尝试在同一线程上恢复代码的执行。

来自the documentation

continueOnCapturedContext

类型:System.Boolean

true 尝试将延续编组回捕获的原始上下文;否则,false

尝试改用ConfigureAwait(true)

【讨论】:

  • 或者,根本不要调用ConfigureAwait - 默认行为是模仿ConfigureAwait(true)
  • 那是相当令人惊讶的。你能确定你真的不在同一个线程上吗?使用 Thread 调试工具,您可以获取它们的 ID 甚至命名它们。如果它不起作用,您可以尝试使用 FromCurrentSynchronizationContext 继续使用,而不是等待任务,但我认为等待已经在这样做了。
【解决方案3】:

反序列化发生在另一个线程中。它打开了自己的线程

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-11
    • 2013-01-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 2015-05-06
    相关资源
    最近更新 更多