2
votes

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:...

3
Once you await something, you may well be running on a different thread once you resume. Not sure why that matters though in this case.Eric J.
Try it without the ConfigureAwait(false).MickyD
@bto.rdz Please post your entire code, because it's almost definitely not related to your snippet above. Somewhere, there will be a piece of code interacting with a UI component. At the very least, the exception is happening on your PostAsync, which means your response code which you've posted hasn't even executed yet.Rob♦
@EricJ. normally code after await will run on original thread for WinForms/WPF/ASP.Net... Indeed it is not the case in OP's code as they explicitly request not to come back to original thread (twice) - once with Task.Run and just to be absolutely sure with ConfigureAwait(false)...Alexei Levenkov
I reckon take the Task.Run, etc out of the picture here - just await things and don't use ConfigureAwait(false). You won't be switching to other logical call contexts then and perhaps it'll all just be fine. If you do then need to spin up a separate thread, and thus have things run genuinely in parallel (rather than allowing overlapping response via async/await), introduce it gently and see what causes your failure to return.Ian Yates

3 Answers

2
votes

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

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.

0
votes

Deserilization happens in another thread. It opens own thread