Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CultureInfo in Dotnet Core #48077

Closed
kurtisane opened this issue Feb 9, 2021 · 7 comments
Closed

CultureInfo in Dotnet Core #48077

kurtisane opened this issue Feb 9, 2021 · 7 comments
Labels
area-System.Globalization question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@kurtisane
Copy link

Hi,
I'm quite confused regarding CultureInfo, Parallesism, Scopes etc.
When a CultureInfo is set, it is set for a thread. For my understanding all Tasks that run in a Thread get the CultureInfo of the Thread they run in and when any of the Task changes the CultureInfo it is changing the CultureInfo of the whole Thread and therefore all of the Tasks. I have the assumption that scopes don't run in seperate threads and more in seperate tasks. Is that right ?
That would mean that if a scope changes the CultureInfo at any time it could effect parallel running scopes ?
Since the RequestCultureProviders also just change the CultureInfo of the current Thread could that effect across scopes ??? I'm really confused.

Example :

  1. Scope No1 (Task 1, Thread 1) gets created
  2. Scope No1 sets CultureInfo to en-US
  3. Scope No2 (Task 2, Thread 1) gets created
  4. Scope No2 sets CultureInfo to de-DE
  5. Option A : Scope No1 accesses Resource File
    Option B : Scope No1 creates multiple Tasks

What are the CultureInfos for Option A and Option B at those moments ?
How does Dotnet Core handle this ?
How does Dotnet Core seperate scopes ?

@mairaw mairaw transferred this issue from dotnet/core Feb 9, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Globalization untriaged New issue has not been triaged by the area owner labels Feb 9, 2021
@ghost
Copy link

ghost commented Feb 9, 2021

Tagging subscribers to this area: @tarekgh, @safern, @krwq
See info in area-owners.md if you want to be subscribed.

Issue Details

Hi,
I'm quite confused regarding CultureInfo, Parallesism, Scopes etc.
When a CultureInfo is set, it is set for a thread. For my understanding all Tasks that run in a Thread get the CultureInfo of the Thread they run in and when any of the Task changes the CultureInfo it is changing the CultureInfo of the whole Thread and therefore all of the Tasks. I have the assumption that scopes don't run in seperate threads and more in seperate tasks. Is that right ?
That would mean that if a scope changes the CultureInfo at any time it could effect parallel running scopes ?
Since the RequestCultureProviders also just change the CultureInfo of the current Thread could that effect across scopes ??? I'm really confused.

Example :

  1. Scope No1 (Task 1, Thread 1) gets created
  2. Scope No1 sets CultureInfo to en-US
  3. Scope No2 (Task 2, Thread 1) gets created
  4. Scope No2 sets CultureInfo to de-DE
  5. Option A : Scope No1 accesses Resource File
    Option B : Scope No1 creates multiple Tasks

What are the CultureInfos for Option A and Option B at those moments ?
How does Dotnet Core handle this ?
How does Dotnet Core seperate scopes ?

Author: kurtisane
Assignees: -
Labels:

area-System.Globalization, untriaged

Milestone: -

@ghost ghost added this to Untriaged in ML, Extensions, Globalization, etc, POD. Feb 9, 2021
@safern safern added question Answer questions and provide assistance, not an issue with source code or documentation. and removed untriaged New issue has not been triaged by the area owner labels Feb 9, 2021
@safern safern added this to the Future milestone Feb 9, 2021
@ghost ghost moved this from Untriaged to Future in ML, Extensions, Globalization, etc, POD. Feb 9, 2021
@tarekgh
Copy link
Member

tarekgh commented Feb 9, 2021

@kurtisane the current cultures is handled using the asynclocal. It is useful to review how asynclocal work in general.

Current cultures (either CurrentCulture or CurrentUICulture) are stored as asynclocal. That means it will be stored in the async context of the executing thread. Every time you create a new child task, the current async context will be copied to this child task. changing the value in the child task wouldn't affect the parent task.

In your example, Scope 1 is created and setting en-US. When creating the scope 2, the async context will be copied to the scope 2 and scope 2 will have en-US too. When setting the current culture as de-DE on scope 2, that will affect only the context of scope 2 and will not have any effect on the scope 1 context. i.e. scope 1 will stay using en-US.

Option A : Scope No1 accesses Resource File

Would use en-US.

Option B : Scope No1 creates multiple Tasks

It will copy the current culture value to the child tasks. i.e. the child tasks will have en-US.

I know you are asking about .NET Core but here is some more info around this issue:

  • The behavior using asynclocal is introduced in .NET Framework 4.6. Before that the behavior was different as used to store the current cultures on the current running thread. Obviously this design was very broken for the async operations and that is why we switched to use asynclocal back then. On the ,NET Framework we provide a config switch if someone need to go back to the old undesired behavior.
  • The Thread.CurrentCulture/CurrentUICulture is kind of obsolete because it was very confusing because these was instance properties on the Thread class and when called was affecting the current thread and not the thread object instance the property called from. Moving forward we recommend using CultureInfo.CurrentCulture/CurrentUICulture for clarity and simplicity.

I hope this clarify the behavior and let's know if you have any more questions.

@tarekgh tarekgh closed this as completed Feb 9, 2021
ML, Extensions, Globalization, etc, POD. automation moved this from Future to Done Feb 9, 2021
@kurtisane
Copy link
Author

First of all thank you very very much for your time and effort.
I think I understood all of it so far. Also read a little bit about asynclocal.

The last think that is kind of not clear for me is the behavior of static classes, properties etc. :

We have a Resource.Designer.cs. This contains a static CultureInfo and static ResourceManager.
Are they as well seperate from Task to Task ?

@tarekgh
Copy link
Member

tarekgh commented Feb 11, 2021

We have a Resource.Designer.cs. This contains a static CultureInfo and static ResourceManager.
Are they as well seperate from Task to Task ?

If you share some code, I can tell you the exact behavior. But in general, if the ResourceManager instance in Resource.Designer.cs is always using the static CultureInfo, then changing this static value will get applied regardless of the tasks. But if the ResourceManager is going to use CurrentUICulture, then you'll get the asynclocal behavior I described earlier.

Feel free to send any more questions if there is anything unclear here.

@kurtisane
Copy link
Author

kurtisane commented Feb 11, 2021

So we have this regular Designer.cs.
And we are queueing couple of "Jobs" via our HostetService and process them in paralell with Tasks. Each "Job" has its own Culture that needs to be used when it is processed.

Now when the Tasks are running in parallel and use the same Designer.cs and therefore the same static Properties are they going to effect each other. I'm not sure if it is even possible, but I'm not initializing any ErrorMessageResources Object.
My calls inside each Task is basically :
ErrorMessagesResources.Account_AccountInactivated

namespace MyDomain.Common.Logic.Resources {
    using System;
    
    
    /// <summary>
    ///   A strongly-typed resource class, for looking up localized strings, etc.
    /// </summary>
    // This class was auto-generated by the StronglyTypedResourceBuilder
    // class via a tool like ResGen or Visual Studio.
    // To add or remove a member, edit your .ResX file then rerun ResGen
    // with the /str option, or rebuild your VS project.
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    public class ErrorMessageResources {
        
        private static global::System.Resources.ResourceManager resourceMan;
        
        private static global::System.Globalization.CultureInfo resourceCulture;
        
        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal ErrorMessageResources() {
        }
        
        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MyDomain.Common.Resources.ErrorMessageResources", typeof(ErrorMessageResources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
        
        /// <summary>
        ///   Overrides the current thread's CurrentUICulture property for all
        ///   resource lookups using this strongly typed resource class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }
        
        /// <summary>
        ///   Looks up a localized string similar to Account has been inactivated.
        /// </summary>
        public static string Account_AccountInactivated {
            get {
                return ResourceManager.GetString("Account_AccountInactivated", resourceCulture);
            }
        }
    }
}

@tarekgh
Copy link
Member

tarekgh commented Feb 11, 2021

My calls inside each Task is basically: ErrorMessagesResources.Account_AccountInactivated

This call is using resourceCulture. The default value for this culture inside ErrorMessageResources class is null. So when Account_AccountInactivated calls ResourceManager.GetString("Account_AccountInactivated", resourceCulture) it will pass null as the second parameter and that will make the resource manager to use CurrentUICulture and you'll get the asynclocal behavior I described before.

But, at any point if your code will set ErrorMessageResources.Culture, this will change the behavior and whatever culture value you set will be applied to all tasks when calling ErrorMessagesResources.Account_AccountInactivated. i.e. ErrorMessagesResources.Account_AccountInactivated at that time will always return the resources matched the culture value you set regardless running inside any task.

@kurtisane
Copy link
Author

Thank you, so so much. I was struggeling with this so hard.

Perfect explaination !

@dotnet dotnet locked as resolved and limited conversation to collaborators Mar 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Globalization question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

3 participants