bto.rdz
bto.rdz

Reputation: 6720

Threading error while deserializing JSON

So I made a request wrapper for my clients, and everything was working fine. But suddenly (I have no clue why) JsonConvert.DeserializeObject<T>(c) throws the classic exception

The calling thread cannot access this object because a different thread owns it

Well I don't see any other thread but this one. All of them are local variables and according to Newtonsoft https://github.com/JamesNK/Newtonsoft.Json/issues/469

A new JsonSerializerInternalReader is created each time you deserialize an object

Do you have any clue where is the another thread this exception is talking about?

    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;
        });

Already tried this without luck.

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

Update

To be sure that that line is the one throwing I made this:

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

And everything is running fine. Any clues?

Update 2

Stack trace

What I see here is that the serializer is trying to access the main window. I have to say that this is happening inside a ShowDialog() window, so I guess the main window is not available. But I'm not sure if I am correct or how to fix this.

at System.Windows.Threading.Dispatcher.VerifyAccess() at System.Windows.Application.get_MainWindow() at ControliWindows.Globals.Controli.get_Window() in C:... at ControliWindows.Globals.Framework.Modalizer.SaveableModel1..ctor() in C:... at ControliWindows.Views.Modals.AccountMm..ctor() in C:... at CreateControliWindows.Views.Modals.AccountMm() at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value) at ControliWindows.Globals.Connection.<>c__DisplayClass39_0`1.<<_reqWrapper>b__0>d.MoveNext() in C:...

Upvotes: 2

Views: 2375

Answers (3)

onur
onur

Reputation: 374

Deserilization happens in another thread. It opens own thread

Upvotes: 0

Scott Chamberlain
Scott Chamberlain

Reputation: 127593

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

That is the source of your problem. Here is the series of events that is happening:

  • Your T is a ControliWindows.Views.Modals.AccountMm and DeserializeObject must make a new one of them
  • AccountMm's constructor it is creating a ControliWindows.Globals.Framework.Modalizer.SaveableModel1
  • SaveableModel1's constructor it is reading the property ControliWindows.Globals.Controli.Window
  • in Controli.Window it is reading the property System.Windows.Application.Window
  • Application.Window can only be read from the UI thread, this whole chain of events happens on a Threadpool thread and causes your exception.

The easiest solution is have ControliWindows.Globals.Controli.Window detect if it is not on the UI thread and if it is not invoke to the UI to get the value of 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);
            }
        }
    }
}

Upvotes: 2

Fabio Salvalai
Fabio Salvalai

Reputation: 2509

By using ConfigureAwait(false) you are explicitly telling your await to not try to resume the execution of the code on the same thread after performing your await.

From the documentation:

continueOnCapturedContext

Type: System.Boolean

true to attempt to marshal the continuation back to the original context captured; otherwise, false.

Try using ConfigureAwait(true) instead.

Upvotes: 0

Related Questions