C# Asynchronous Task for a Stock Ticker

2019-04-14 19:12发布

I've been trying to learn more about asynchronous tasks and threading but not making a ton of headway.

I'm trying to load an "Engine" type of thread that will run in the background upon launch and be able to access the UI Thread to update variables, without hanging the UI Thread.

In the below code, Engine is called, and a Ticker object is created which holds the current value of (Litecoin/USD) called Last, also holds several other values that would be useful. This code successfully assigns the current value to label1.text. I don't necessarily need code but what approach would I take to create a ticker object in the background every second and update the UI thread with each new Ticker objects values.

Is this a good case for a background worker?

    private void Form1_Load(object sender, EventArgs e)
    {
        Engine();
    }
    private void Engine()
    {
        Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
        label1.Text = "LTC/USD:" + ltcusd.Last;
    }

EDIT: If I do the following, label1 throws an InvalidOperationException due to a Cross-thread operation attempt (label1 in the UI thread).

    private void Form1_Load(object sender, EventArgs e)
    {
        var t = Task.Factory.StartNew(() => Engine());
        t.Start();
    }
    private void Engine()
    {
        while (true)
        {
            Thread.Sleep(1000);
            Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
            label1.Text = "LTC/USD: " + ltcusd.Last;
        }
    }

3条回答
Fickle 薄情
2楼-- · 2019-04-14 19:26

Using async/await, the simplest way of getting an "asynchronous" sort of API is to invoke a new task. It's not great, but it'll make things simpler. I would probably create a new class which basically wrapped all the BtceApi methods in tasks:

public class BtceApiAsync
{
    public Task<Ticker> GetTickerAsync(BtcePair pair)
    {
        return Task.Run(() => BtceApi.GetTicker(pair));
    }

    // etc
}

Then you can use a timer which fires once per second, which will start off a new task and update the UI appropriately:

// Keep a field of type System.Windows.Forms.Timer
timer = new Timer();
timer.Interval = 1000;
timer.Tick += DisplayTicker;
timer.Start();

...

private async void DisplayTicker(object sender, EventArgs e)
{
    Ticker ticker = await BtceApiAsync.GetTickerAsync(BtcePair.LtcUsd);
    label1.Text = "LTC/USD: " + ltcusd.Last;
}

Note that this doesn't mean the screen will be updated once per second... there will be a new task started once per second, and as soon as each task completes, the UI will be updated.

The use of await here - from an async method started on the UI thread - means you don't need to worry about using the UI; the whole async method will execute on the UI thread, even though the fetch itself happens in a different thread.

查看更多
Deceive 欺骗
3楼-- · 2019-04-14 19:28

I suppose this is Windows Forms. You could do it "old school style" and set the label text on the UI thread, and you can do that by passing delegate to the BeginInvoke or Invoke method.

   private void Engine()
   {
      while (true)
     {
        Thread.Sleep(1000);
        Ticker ltcusd = BtceApi.GetTicker(BtcePair.LtcUsd);
        UpdateText("LTC/USD: " + ltcusd.Last);
      }
    }
    private void UpdateText(string text)
    {
        //Inspect if the method is executing on background thread
        if (InvokeRequired)
        {
           //we are on background thread, use BeginInvoke to pass delegate to the UI thread
            BeginInvoke(new Action(()=>UpdateText(text)));
        }
        else
        {
           //we are on UI thread, it's ok to change UI
           label1.Text = text;            
        }
    }
查看更多
霸刀☆藐视天下
4楼-- · 2019-04-14 19:39

You can try ContinueWith to update the Label at the end of the task. If you want to update it event before the task ends then raise an event which is registered by on the UI thread. The event can then update the label.

查看更多
登录 后发表回答