简体   繁体   中英

Async method not reporting back correctly from list of tasks

I have four tasks to run within the MainViewModel of a C# WPF Windows Application. All the tasks call the same Update method, using a different parameter value for each execution. I call the Update method in the MainViewModel like this:

//UpdateStatus is another method in the MainViewModel (see below)
var progress = new Progress<string>(data => UpdateStatus(data));
var tasks = new List<Task>();

//There are some DataTables that I have summarised here to have names that start with "dt_" as seen below
tasks.Add(Task.Run(() => FileSchema_Tradacom_tab = FileSchema.Update_FileSchema(dt_T, FileStyle.Tradacom, progress)));
tasks.Add(Task.Run(() => FileSchema_Edifact_tab = FileSchema.Update_FileSchema(dt_E, FileStyle.EdiFact, progress)));
tasks.Add(Task.Run(() => FileSchema_DepotList_tab = FileSchema.Update_FileSchemac(dt_D, FileStyle.DepotList, progress)));
tasks.Add(Task.Run(() => FileSchema_ProductList_tab = FileSchema.Update_FileSchema(dt_P, FileStyle.ProductList, progress)));

await Task.WhenAll(tasks);

[... code continues...]

Inside the method, the FileStyle parameter is used to report back the status for that execution.

public static DataTable Update_FileSchema(DataTable dt, FileStyle fileStyle,IProgress<string> progress)
{
    progress.Report($"1[{StatusCode.Connecting}]Connecting...");

    [...Code to retrieve data from csv files and SQL queries...]

    //note the use of the fileStyle parameter to add detail
    progress.Report($"0[{StatusCode.Connected}]File Schema ({fileStyle}) Retrieved");
}

finally, the method that Updates the Status Pane on my application window looks like this is also in the MainViewModel

private void UpdateStatus(string message)
{
    //Sets the values of various properties of the MainViewModel, including Status and StatusMessage,
    //Which are displayed in a WPF TextBlock
    if (message.Contains("]"))
    {
        try
        {
            this.Status = this.Status ?? "";
            //The message comes after the bit in square brackets
            var newMessage = $"{(Status.Length == 0? "":"\n")}{message.Substring(message.IndexOf("]") + 1)}";
            //The new line indicator (1 or 0) comes before the square brackets
            var addLine = message.Substring(0, message.IndexOf("[")) == "1";
            //The code is the bit inside the square brackets (gotta get the start and end points of this one)
            var codeFrom = message.IndexOf("[") + 1;
            var codeTo = message.LastIndexOf("]");
            var codeValue = message.Substring(codeFrom, codeTo - codeFrom);
            this.StatusCode = (StatusCode)Enum.Parse(typeof(StatusCode), codeValue);

            [...More code that goes on to update other properties of this MainViewModel...]
}

I had hoped when running this to see four identifiably different reports in the application status window, reporting each of the four tasks. Instead, although I get four reports but they all say the same thing - I see "File Schema (DepotList)" repeated four times.

If I add a Thread.Sleep(10000); between the two reports in the Update method, I see that each of the statuses is updating correct line in the Status Pane Textblock in that when it is not adding a new line, it replaces the correct "Connecting..." line that was inserted earlier in the method execution.

I don't understand why the reporter is not taking the different values of the fileStyle parameter and spitting them back out in the progress report; surely each concurrent execution of the method runs separately with separate values in the parameter? In fact I see that this is the case when I run a SQL Profiler, I see four separate queries being made each with a different fileStyle. Any pointers would be extremely welcome.

I don't know if this will fix your problem, but you could benefit by passing structured data through the Progress , instead of passing strings that later have to be laboriously parsed. ValueTuple s are excellent for this job:

var progress = new Progress<(StatusCode, string)>(data => UpdateStatus(data));
public static DataTable Update_FileSchema(DataTable dt, FileStyle fileStyle,
    IProgress<(StatusCode, string)> progress)
{
    progress.Report((StatusCode.Connecting, $"Connecting..."));
    //...
    progress.Report((StatusCode.Connected, $"File Schema ({fileStyle}) Retrieved"));
}
private void UpdateStatus((StatusCode, string) progressData)
{
    var (statusCode, message) = progressData;
    //...
}

Another thing that looks suspicious is assigning the FileSchema_Tradacom_tab etc properties from the background thread. My assumption is that these properties are tied with UI elements. If that's the case, you should do the assignments on the UI thread. Here is one way to do it:

var factories = new List<Func<Task>>();
factories.Add(async () => FileSchema_Tradacom_tab = await Task.Run(
    () => FileSchema.Update_FileSchema(dt_T, FileStyle.Tradacom, progress)));
factories.Add(async () => FileSchema_Edifact_tab = await Task.Run(
    () => FileSchema.Update_FileSchema(dt_E, FileStyle.EdiFact, progress)));
factories.Add(async () => FileSchema_DepotList_tab = await Task.Run(
    () => FileSchema.Update_FileSchemac(dt_D, FileStyle.DepotList, progress)));
factories.Add(async () => FileSchema_ProductList_tab = await Task.Run(
    () => FileSchema.Update_FileSchema(dt_P, FileStyle.ProductList, progress)));
Task[] tasks = factories.Select(f => f()).ToArray();
await Task.WhenAll(tasks);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM