Thread.CurrentThread.CurrentCulture not working in a thread inside a threadpool
Asked Answered
I

2

6

I have a method which will be called inside a thread and those threads are managed by threadpool. The method is calling a DLL's method which unfortunately requires a specific locale for being performed correctly.

Before puting this method to be ran by threadpool, I've tested it while running in application's main thread and also while I manually manage the threads and it works fine, but when I put it into work inside a threadpool, locale is not applied and consequently the method does not behave correctly.

Here is the part of the method which should be affected by locale change (but not behave well):

CultureInfo before = Thread.CurrentThread.CurrentCulture;

Thread.CurrentThread.CurrentCulture = new CultureInfo("fa-IR");
int result = tsms.SendSMS(smsTask.MobileNumber.MobileNumberInString, smsTask.Message);
Thread.CurrentThread.CurrentUICulture = before;

and here is the threadpool creating structure:

foreach (SMSTask smsTask in tasksList)
{
    if (this.threadsCount < this.threadPoolSize)
    {
        this.threadsCount++;                        
        ThreadPool.QueueUserWorkItem(new WaitCallback(SendMessage), (object)smsTask);
    }
}

I also tried setting locale to thread object like below but it didn't solve the problem: (line 2 and 3):

threadObject = new Thread(new ThreadStart(TaskProcessingThreadFunction));
threadObject.CurrentCulture = new CultureInfo("fa-IR");
threadObject.CurrentUICulture = new CultureInfo("fa-IR");
threadObject.Start();

Please guide me on how should I get the correct result while this method is being ran inside threadpool.

Updated Version:

this.isTerminated = false;
            Thread.Sleep(1000);
            while (!this.isTerminated)
            {
                Thread.Sleep(1000);
                IList<SMSTask> tasksList = dataProvider.GetTasks(this.minimumRetryTimeInSeconds);

                if (tasksList == null || tasksList.Count < 1)
                    continue;
                singleTaskConsoleObject(" " + tasksList.Count + " task(s) fetched for sending.");

                var cultureToUse = new System.Globalization.CultureInfo("fa-IR");
                var currentThread = System.Threading.Thread.CurrentThread;
                currentThread.CurrentCulture = cultureToUse;
                currentThread.CurrentUICulture = cultureToUse;

                var currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
                foreach (SMSTask smsTask in tasksList)
                {
                    if (this.threadsCount < this.threadPoolSize)
                    {
                        this.threadsCount++;
                        smsTask.Iden = currentIdentity;
                        ThreadPool.QueueUserWorkItem(new WaitCallback(SendMessage), (object)smsTask);
                    }
                }
                while (this.threadsCount > 0)
                    Thread.Sleep(50);
            }

Other Methods:

private void SendMessage(object smsTask)
        {
            System.Security.Principal.WindowsImpersonationContext impersonationContext = null;
            try
            {
                SMSTask smsTaskEntity = (SMSTask)smsTask;
                impersonationContext = ((System.Security.Principal.WindowsIdentity)(smsTaskEntity.Iden)).Impersonate();

                SmsSender smsSender = new SmsSender();
                SMSSendingResponse response = smsSender.SendSMS(smsTaskEntity);
                bool loggingResult = dataProvider.UpdateResponse(response);
                singleTaskGridObject(response, loggingResult);

                this.threadsCount--;
            }
            finally
            {
                if (impersonationContext != null)
                {
                    impersonationContext.Undo();
                }
            }
        }

And this separate class:

public SMSSendingResponse SendSMS(SMSTask smsTask)
{

        TSMSLIB_TLB.TSMS_Tooba tsms = new TSMS_Tooba();

        SendingResult sendingResult = SendingResult.Initial_Condition;
        try
        {

            System.Globalization.CultureInfo before = System.Threading.Thread.CurrentThread.CurrentCulture;

            System.Threading.Thread.CurrentThread.CurrentCulture =
                new System.Globalization.CultureInfo("fa-IR");

            string msg = string.Format(System.Threading.Thread.CurrentThread.CurrentCulture, smsTask.Message);

            int result = tsms.SendSMS(smsTask.MobileNumber.MobileNumberInString, msg);
            System.Threading.Thread.CurrentThread.CurrentUICulture = before;
            if (result > 0)
            {                    
                return new SMSSendingResponse(smsTask, SMSDeclarations.SendingStatus.SentSuccessfully, result.ToString());
            }
            else
            {
                foreach (SendingResult sResult in Enum.GetValues(typeof(SendingResult)))
                {
                    if (result == (int)sResult)
                    {
                        sendingResult = sResult;
                    }
                }
                return new SMSSendingResponse(smsTask, SMSDeclarations.SendingStatus.SendingFailed,
                    result.ToString(), sendingResult.ToString());
            }
        }
        catch (Exception ex)
        {
            return new SMSSendingResponse(smsTask, SMSDeclarations.SendingStatus.SendingFailed,
                    "0".ToString(), "Exception occured");
        }

    }
Innovation answered 1/1, 2014 at 18:51 Comment(11)
try setting it through a DispatcherImaginative
...or just plain set Thread.CurrentThread.CurrentCulture inside TaskProcessingThreadFunction...?Look
that didn't help. So weird.Innovation
The confusing part is that it is truely related to how the thread pool is managing the locale of threads because otherwise the locale applies well.Innovation
Dispatcher does not belong to win forms.Innovation
Infamous problem, culture doesn't flow from one thread to another. You must change it on thread that actually executes the code. The last snippet should have worked, there's probably more going on that we can't see.Barksdale
Isn't the problem here that SendSMS most likely fires off something on another thread which won't be using the same culture of the calling thread? (unless it's being set internally). You don't provide any implementation detail of that method so can't say for sure, but I presume that is what the problem is.Boyce
@HansPassant the first part of the code sample is actually the worker method of the thread. I've put it there and I'v set the size of threadpool to 1 but still not being applied.Innovation
@Boyce you're right but the whole change is just for one line (int result = ...)Innovation
The code was shortened just a little too much. Show clearly which code is in SendMessage, TaskProcessingThreadFunction etc.Champlin
@HenkHolterman Now I've brought the whole codeInnovation
L
2

The culture must be applied inside the actual method called by the thread.

A simple way should be to wrap the method to be executed on the thread pool inside another method that applies the CultureInfo from the thread enqueuing the job to the thread pool thread before executing the original method, something like;

private static WaitCallback PropagateCulture(WaitCallback action)
{
    var currentCulture   = Thread.CurrentThread.CurrentCulture;
    var currentUiCulture = Thread.CurrentThread.CurrentUICulture;
    return (x) =>
    {
        Thread.CurrentThread.CurrentCulture   = currentCulture;
        Thread.CurrentThread.CurrentUICulture = currentUiCulture;
        action(x);
    };
}

Given that method, you just submit to the threadpool using;

ThreadPool.QueueUserWorkItem(PropagateCulture(SendMessage), (object)smsTask);

(thanks to Aidiakapi for pointing out WaitCallback in the comments below)

Look answered 1/1, 2014 at 19:5 Comment(11)
+1 for the WrapCulture though it would be better to change Action to WaitCallback since that's the parameter the ThreadPool takes.Amphidiploid
Thanks. please let me check.Innovation
@Amphidiploid I may very well be wrong, but isn't that just for ParameterizedThreadStart? :)Look
I meant as both parameter and return value. The OP said he's using the ThreadPool.QueueUserWorkItem in order to manage these threads. It takes a WaitCallback, so if you have a function WaitCallback WrapCulture(CultureInfo ci, WaitCallback callback) it'd be universally applicable.Amphidiploid
@Amphidiploid Ah, yes, I see what you mean. Will update the answer shortly with your input.Look
@Innovation Could you try the updated version? It should apply better to your existing threadpool submission code.Look
@JoachimIsaksson sure. Please check the question. I'm going to add other parts.Innovation
@Innovation Did you try this out yet? My limited tests show it working well.Look
@JoachimIsaksson: I tested all suggestions you told but unfortunately they didn't solved. It is becoming a very confusing problem for me.Innovation
@Innovation What part of the method called by the thread is misbehaving? Translation/number format/date format/...?Look
@JoachimIsaksson non-latin (unicode) characters are sent like ???? ??? instead of این متنInnovation
B
1

Within your SendMessage method, you need to set both the thread's current culture and it's UICulture:

var cultureToUse = new System.Globalization.CultureInfo("fa-IR");
var currentThread = System.Threading.Thread.CurrentThread;
currentThread.CurrentCulture = cultureToUse;
currentThread.CurrentUICulture = cultureToUse;

If the current culture is not static, then you must pass it from the calling thread to the queued worker thread.

In this scenario, I would add a culture parameter to the SMSTask class if you control it or, if not, add a wrapper class that contains both the SMSTask and the culture to use if you do not control it and use the new class as the parameter to the SendMessage method.

Update

Here is another thought: if SendMessage works correctly while running in the main thread context, but changing the culture does not affect the results when running in a thread pool, the issue may be that the method picks information up from the current user.

You can test this theory by saving the current identity before queueing the threadpool request and passing as a parameter to your SendMessage method:

var currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
// ToDo: Store current identity in a parameter for SendMessage

Within the SendMessage method, you can retrieve the identity of the calling thread and impersonate that user, perform your work, then undo the impersonation:

    System.Security.Principal.WindowsImpersonationContext impersonationContext = null;
    try {
        // Get the current identity from the SendMessage parameter and use it to impersonate that user on this thread
        impersonationContext = currentIdentity.Impersonate();

        // Do work

    } finally {
        // Undo the impersonation
        if (impersonationContext != null) {
            impersonationContext.Undo();
        }
    }
Blondellblondelle answered 1/1, 2014 at 19:6 Comment(10)
That's debatable and all depends on exactly what aspects of the culture the method needs (you don't always need to set both).Boyce
@James: Agreed, but since it wasn't working to just set the culture, it is highly likely that the UICulture is in play. In addition, many, many people are not aware of the existence of or the need to set UICulture.Blondellblondelle
I copied the code to every single part of the structure but unfortunately it didn't affected. When not setting the locale, sent message is arriving to the cell phone like ???? ??? instead of proper characters.Innovation
@Farshid: in that case, it sounds to me like the SendSMS method is not behaving correctly. Is this a method that you control or is it from a third party?Blondellblondelle
Yes it is from an awefull 3rd party but it behaves well when not in threadpool.Innovation
Maybe if i can apply localization to the argument it would be solved without applying CurrentCulture. Is it possible?Innovation
@Farshid: I just updated the answer with another idea that may resolve the issue.Blondellblondelle
@Blondellblondelle Thank you. I'm going to check it.Innovation
@Blondellblondelle It didn't solve. However I added the whole implementation to the question.Innovation
@Farshid: I just looked at the revised code you posted. Only suggestion I have is to move the setting of the currentUICulture before the tsms.SendSMS line instead of after it.Blondellblondelle

© 2022 - 2024 — McMap. All rights reserved.