Updating UI Control from ActionBlock

僤鯓⒐⒋嵵緔 提交于 2019-12-10 20:21:11

问题


I have been trying to understand the TPL Dataflow by creating a sample application. One of thing that I have been trying to do is to update a TextBox control from ActionBlock. The reason for using TPL Dataflow is to perform parallel asynchronous operation while keeping the order. The following function is written by me,

private TaskScheduler scheduler = null;

public Form1()
    {
        this.scheduler = TaskScheduler.FromCurrentSynchronizationContext();
        InitializeComponent();
    }

public async void btnTPLDataFlow_Click(object sender, EventArgs e)
    {
        Stopwatch watch = new Stopwatch();
        watch.Start();

        txtOutput.Clear();

        ExecutionDataflowBlockOptions execOptions = new ExecutionDataflowBlockOptions();
        execOptions.MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded;
        execOptions.TaskScheduler = scheduler;

        ActionBlock<int> actionBlock = new ActionBlock<int>(async v =>
        {
            bool x = await InsertIntoDatabaseAsync(v);

            if (x)
                txtOutput.Text += "Value Inserted for: " + v + Environment.NewLine;
            else
                txtOutput.Text += "Value Failed for: " + v + Environment.NewLine;

        }, execOptions);


        for (int i = 1; i <= 200; i++)
        {
            actionBlock.Post(i);
        }

        actionBlock.Complete();
        await actionBlock.Completion;            

        watch.Stop();
        lblTPLDataFlow.Text = Convert.ToString(watch.ElapsedMilliseconds / 1000);
    }


private async Task<bool> InsertIntoDatabaseAsync(int id)
    {
        try
        {
            string connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:\\TPLDatabase.accdb;Persist Security Info=False;";

            using (OleDbConnection conn = new OleDbConnection(connString))
            {
                string commandText = "INSERT INTO tblRecords (ProductName, ProductDescription, IsProcessed) VALUES (@ProductName, @ProductDescription, @IsProcessed)";

                await conn.OpenAsync();
                using (OleDbCommand command = new OleDbCommand(commandText, conn))
                {
                    command.CommandType = CommandType.Text;

                    command.Parameters.AddWithValue("@ProductName", "Product " + id);
                    command.Parameters.AddWithValue("@ProductDescription", "Description " + id);
                    command.Parameters.AddWithValue("@IsProcessed", false);

                    if (await command.ExecuteNonQueryAsync() > 0)
                        return true;
                    else
                        return false;
                }
            }
        }
        catch
        {
            return false;
        }
    }

Now the above piece of code runs just fine. It inserts the records in order in my sample MS Access database and updates the UI in order also. But the problem with this is that it blocks the UI which is understandable as I am using the TaskScheduler.FromCurrentSynchronizationContext which will be updating TextBox on UI Thread.

I made a small change in the code and remove the scheduler from the ExecutionDataflowBlockOptions. Instead I update the UI with the following code,

txtOutput.Invoke(new MethodInvoker(delegate
            {
                if (x)
                    txtOutput.Text += "Value Inserted for: " + v + Environment.NewLine;
                else
                   txtOutput.Text += "Value Failed for: " + v + Environment.NewLine;
            }));

Now this change does not freezes the UI but the order of the values in the database and order of values displayed in TextBox is disturbed badly. The new order is like this,

ID   ProductName    ProductDescription  IsProcessed
6847    Product 6     Description 6       False
6848    Product 7     Description 7       False
6849    Product 8     Description 8       False
6850    Product 10    Description 10      False
6851    Product 11    Description 11      False
6852    Product 12    Description 12      False
6853    Product 9     Description 9       False
6854    Product 13    Description 13      False
6855    Product 14    Description 14      False

Now what can be the best way to update the UI in my scenario and also keeping the order.


回答1:


TPL Dataflow blocks preserve the order of the input when they output. They don't preserve the order of execution while inside the block, that's why you see everything out of order.

What you probably want to do is replace the ActionBlock with a TransformBlock to actually do the hard work concurrently and link it with an ActionBlock that updates the UI one at a time.

You can also have this block run on the UI thread so you wouldn't need to use Invoke:

var transformBlock = new TransformBlock<int, int>(
    v => InsertIntoDatabaseAsync(v), 
    execOptions);

var actionBlock = new ActionBlock<int>(x =>
{
    if (x)
        txtOutput.Text += "Value Inserted for: " + v + Environment.NewLine;
    else
        txtOutput.Text += "Value Failed for: " + v + Environment.NewLine;
}, new ExecutionDataflowBlockOptions { TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext() })

transformBlock.LinkTo(ActionBlock, new DataflowLinkOptions { PropagateCompletion = true } );


来源:https://stackoverflow.com/questions/32089932/updating-ui-control-from-actionblock

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!