Get Value from async Method? - c#

I want to have the returned value from the send Operation which is a string and use it in the public MainPage() section. I tried this way, bot doesn´t work. Any idea how to get this value out of the send() Method?
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
string stringData = "";
stringData = "aktion=getBenutzer&name=" + Login.getBenutzername();
//send(stringData);
//textBlock1.Text = getContentOfSendOperation();
button.Foreground = Einstellungen.getBrush();
button1.Foreground = Einstellungen.getBrush();
button2.Foreground = Einstellungen.getBrush();
stringData = "aktion=getMitarbeiterListe";
//string mitarbeiterListe = getContentOfSendOperation();
var task = send(stringData);
string mitarbeiterListe = task.Result;
textBlock1.Text = mitarbeiterListe;
//comboBox.Items.Add()
}
public Frame globalFrame { get { return _mainFrame; } }
private void button_Click(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Datenbank));
}
public async Task<String> send(string stringData)
{
System.Net.Http.HttpClient oHttpClient = new System.Net.Http.HttpClient();
Uri uri = new Uri("*********");
oHttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("moralsKite/DesktopTestClient");
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Post, uri);
request.Content = new StringContent(stringData, Encoding.UTF8, "application/x-www-form-urlencoded");
var reponse = await oHttpClient.SendAsync(request);
if (reponse.IsSuccessStatusCode)
{
return "??";
//return await reponse.Content.ReadAsStringAsync();
}
return "!!";
}
private void button1_Click_1(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Einstellungen));
}
private void button2_Click_1(object sender, RoutedEventArgs e)
{
_mainFrame.Navigate(typeof(Ueber));
}
}
}

You cannot run asynchronous operation and await it in constructor. In your example the task can run little longer (varying on signal and so on), the constructor of a class should be fast. Better subscribe to one of the page's events like Loaded and put your work there:
public MainPage()
{
this.InitializeComponent();
// rest of code
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// Following Peter Torr's comment - Loaded event can be fired multiple times
// for example once you navigate back to the page
this.Loaded -= MainPage_Loaded; // deregister from event if you want to run it once
stringData = "aktion=getMitarbeiterListe";
// in your send method uncomment the line:
// return await reponse.Content.ReadAsStringAsync();
// then it will return asynchronously the content as string and can be used like this:
string mitarbeiterListe = await send(stringData);
}
Events can be async then there shouldn't be problems, you may also implement an information for user that something is loading in the background.

Instead of :
var task = send(stringData);
string mitarbeiterListe = task.Result;
use
string mitarbeiterListe = await send(stringData);
Make the method where you call send data async.
I do not recommend to call gathering of data in constructor.

Related

How to get json response with WebView2

I wanna get a json from the response's body of this API:
// http://localhost:3000/api/auth/[token]
export default function Auth(request, response) {
response.status(200).json({ token: request.query})
}
Trying the WebView.CoreWebView2.WebResourceResponseReceived event fires just once and the event arg's request Uri parameter is "http://localhost:3000/favicon.ico".
How can I get the response content?
What I did:
public partial class SignInUserControl : UserControl
{
public SignInUserControl()
{
InitializeComponent();
InitWebView();
}
async void InitWebView()
{
await WebView.EnsureCoreWebView2Async(null);
WebView.CoreWebView2.WebResourceResponseReceived += CoreWebView2_WebResourceResponseReceived;
}
async void CoreWebView2_WebResourceResponseReceived(object sender, CoreWebView2WebResourceResponseReceivedEventArgs e)
{
try
{
Stream stream = await e.Response.GetContentAsync();
TextReader tr = new StreamReader(stream);
string re = tr.ReadToEnd();
}
catch { }
}
}
What I expect:
http://localhost:3000/api/auth/42sad87aWasFGAS
re = {"token":"42sad87aWasFGAS"} // From CoreWebView2_WebResourceResponseReceived method
ps: The WebViewer2 Control is working. So I don't think the problem is related to its initialization.
working example
The problem really was the WebView initialization. 🤦‍♂️
Thanks to #user09938 and #david-risney
What did I do?
I removed the Source property from the Xaml and made these changes:
public partial class SignInUserControl : UserControl
{
public SignInUserControl()
{
InitializeComponent();
InitwebView();
}
private void InitwebView()
{
WebView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;
WebView.EnsureCoreWebView2Async(null).GetAwaiter();
WebView.Source = new Uri("http://localhost:3000/api/auth/NelsonHenrique");
}
private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
WebView.CoreWebView2.WebResourceResponseReceived += CoreWebView2_WebResourceResponseReceived;
}
private void CoreWebView2_WebResourceResponseReceived(object sender, CoreWebView2WebResourceResponseReceivedEventArgs e)
{
var result = e.Response.GetContentAsync().GetAwaiter();
result.OnCompleted(() =>
{
try
{
var res = result.GetResult();
StreamReader reader = new StreamReader(res);
string text = reader.ReadToEnd();
// text: "{\"token\":\"NelsonHenrique\"}"
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
}
}

Updating Winforms Label with Timer and Thread, stock app

Gist of it has probably been asked before, but I'm completely lost so I'm looking for some personal guidance. Been trying to make a stock tracker app for funsies using WinForms and the Yahoo API. Trying to get it so you can input a tracker symbol and it will make a new Label that will keep updating itself every so often. However, it keeps giving me error messages about "Cross-thread operation not valid". I've tried to do some googling, but yeah, completely lost. Here is most of the code, hope you guys can make some sense of it.
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using YahooFinanceApi;
namespace stockpoging4
{
public partial class Form1 : Form
{
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
}
}
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
public async void do_Things(string result)
{
string price;
Label label = null;
if (label == null)
{
price = await getStockPrices(result);
label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.Controls.Add(label);
}
else
{
Thread testThread = new Thread(async delegate ()
{
uiLockingTask();
price = await getStockPrices(result);
label.Text = result + " $" + price;
label.Update();
});
}
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Start();
timer.Elapsed += do_Things(results);
}
private void uiLockingTask() {
Thread.Sleep(5000);
}
}
}
Let me point out several things in your implementation.
You subscribe to timer.Elapsed after timer.Start that might be invalid in case of a short-timer interval
The event handler is called in background that's why you continuously get "Cross-thread operation not valid". UI components should be dispatched correctly from background threads, for example, by calling flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label))); and label.BeginInvoke(new Action(label.Update)). This change already would fix your exception.
Despite the fact that I would implement this functionality in a different way, here I post slightly changed code that just does exactly what you need with some tweaks.
public partial class Form1 : Form
{
Task _runningTask;
CancellationTokenSource _cancellationToken;
public Form1()
{
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo("en-US");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
using (Prompt prompt = new Prompt("Enter the ticker symbol", "Add ticker"))
{
string result = prompt.Result;
result = result.ToUpper();
if (!string.IsNullOrEmpty(result))
{
do_Things(result);
_cancellationToken = new CancellationTokenSource();
_runningTask = StartTimer(() => do_Things(result), _cancellationToken);
}
}
}
private void onCancelClick()
{
_cancellationToken.Cancel();
}
public async Task<string> getStockPrices(string symbol)
{
try
{
var securities = await Yahoo.Symbols(symbol).Fields(Field.RegularMarketPrice).QueryAsync();
var aapl = securities[symbol];
var price = aapl[Field.RegularMarketPrice];
return symbol + " $" + price;
}
catch
{
return "404";
}
}
private async Task StartTimer(Action action, CancellationTokenSource cancellationTokenSource)
{
try
{
while (!cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(1000, cancellationTokenSource.Token);
action();
}
}
catch (OperationCanceledException) { }
}
public async void do_Things(string result)
{
var price = await getStockPrices(result);
var label = new Label() { Name = result, Text = result + " $" + price };
flowLayoutPanel2.BeginInvoke(new Action(() => flowLayoutPanel2.Controls.Add(label)));
}
}
A much easier way is using async these days.
Here is a class which triggers an Action every interval:
public class UITimer : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
// use a private function which returns a task
private async Task Innerloop(TimeSpan interval, Action<UITimer> action)
{
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
await Task.Delay(interval, _cancellationTokenSource.Token);
action(this);
}
}
catch (OperationCanceledException) { }
}
// the constructor calls the private StartTimer, (the first part will run synchroniously, until the away delay)
public UITimer(TimeSpan interval, Action<UITimer> action) =>
_ = Innerloop(interval, action);
// make sure the while loop will stop.
public void Dispose() =>
_cancellationTokenSource?.Cancel();
}
If you work with dotnet 3.0 or higher, you can use the IAsyncDisposable. With this you're able to await the DisposeAsync method, so you can await the _timerTask to be finished.
And I created a new form with this as code behind:
public partial class Form1 : Form
{
private readonly UITimer _uiTimer;
private int _counter;
public Form1()
{
InitializeComponent();
// setup the time and pass the callback action
_uiTimer = new UITimer(TimeSpan.FromSeconds(1), Update);
}
// the orgin timer is passed as parameter.
private void Update(UITimer timer)
{
// do your thing on the UI thread.
_counter++;
label1.Text= _counter.ToString();
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
// make sure the time (whileloop) is stopped.
_uiTimer.Dispose();
}
}
The advantage is, that the callback runs on the UI thread but doesn't block it. The await Task.Delay(..) is using a Timer in the background, but posts the rest of the method/statemachine on the UI thread (because the UI thread has a SynchronizaionContext)
Easy but does the trick ;-)

Async pass multiple parameters into ReportProgress method

How can I pass multiple parameters into the ReportProgress method?
I was following this guide: MSDN to create a progressbar. My code looks like this.
MainWindow.xaml
public User User { get; set; }
public MainWindow()
{
InitializeComponent();
this.User = new User();
this.DataContext = User;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
var progressIndicator = new Progress<int>(ReportProgress); //here is the error
await this.User.ReadUsers(progressIndicator, this.User)
}
void ReportProgress(int value, User pUser)
{
this.User.Val = value;
}
User.xaml
public async Task<bool> ReadUsers(IProgress<int> pProgress, User pUser)
{
for (int i = 1; i < 11; i++)
{
await Task.Delay(500);
pProgress.Report(i, pUser);
}
return true;
}
As you may see, I was trying to add simply a new parameter (User pUser) to the ReportProgress method. Now I get an error inside the Button_Click method (line is marked).
Argument 1: cannot convert from method group into System.Action
best overloaded method match for 'System.Progress.Progress(System.Action)'-method has some invalid arguments
no overload for Report-Method takes 2 arguments
I was trying it like this, because in my real application I will have an ObersableCollection<User>. Is there maybe a better way I should go?
You should pass a second parameter manually, because 'Progress' constructor takes only actions with one argument. Try this:
new Progress<int>(i => ReportProgress(i, this.User));
And remove second argument from 'pProgress.Report' method:
pProgress.Report(i);
I would prefer creating a message and pass multiple values to Report Progress
public class RMssg
{
public int ProgressIndicator { get; set; }
public User userInstance { get; set; }
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
var progressIndicator = new Progress<RMssg>(r => ReportProgress(r));
await this.User.ReadUsers(progressIndicator, this.User);
}
void ReportProgress(RMssg rMssg)
{
this.User.Val = rMssg.ProgressIndicator;
var user = rMssg.userInstance;
}
public async Task<bool> ReadUsers(IProgress<RMssg> pProgress, User pUser)
{
for (int i = 1; i < 11; i++)
{
await Task.Delay(500);
var rMssg = new RMssg() { ProgressIndicator = i, userInstance = pUser };
pProgress.Report(rMssg);
}
return true;
}

How to call WebBrowser Navigate to go through a number of urls?

To collect information on a webpage, I can use the WebBrowser.Navigated event.
First, navigate to the url:
WebBrowser wbCourseOverview = new WebBrowser();
wbCourseOverview.ScriptErrorsSuppressed = true;
wbCourseOverview.Navigate(url);
wbCourseOverview.Navigated += wbCourseOverview_Navigated;
Then process the webpage when Navigated is called:
void wbCourseOverview_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
//Find the control and invoke "Click" event...
}
The difficult part comes when I try to go through a string array of urls.
foreach (var u in courseUrls)
{
WebBrowser wbCourseOverview = new WebBrowser();
wbCourseOverview.ScriptErrorsSuppressed = true;
wbCourseOverview.Navigate(u);
wbCourseOverview.Navigated += wbCourseOverview_Navigated;
}
Here, because the page load takes time, wbCourseOverview_Navigated is never reached.
I tried to use the async await in C#5. Tasks and the Event-based Asynchronous Pattern (EAP) is found in here. Another example can be found in The Task-based Asynchronous Pattern.
The problem is WebClient has async method like DownloadDataAsync and DownloadStringAsync. But there is no NavigateAsync in WebBrowser.
Can any expert give me some advice? Thank you.
There is a post in StackOverflow (here). But, does anyone know how to implement that strut in its answer?
Update again.
Suggested in another post here in StackOverflow,
public static Task WhenDocumentCompleted(this WebBrowser browser)
{
var tcs = new TaskCompletionSource<bool>();
browser.DocumentCompleted += (s, args) => tcs.SetResult(true);
return tcs.Task;
}
So I have:
foreach (var c in courseBriefs)
{
wbCourseOverview.Navigate(c.Url);
await wbCourseOverview.WhenDocumentCompleted();
}
It looks good until my web browser visits the second url.
An attempt was made to transition a task to a final state when it had already completed.
I know I must have made a mistake inside the foreach loop. Because the DocumentCompleted event has not been raised when it loops to the second round. What is the correct way to write this await in a foreach loop?
There is a post in StackOverflow (here). But, does anyone know how to implement that strut in its answer?
Ok, so you want some code with awaiter. I've made two pieces of code.
The first one uses TPL's built-in awaiter:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ProcessUrlsAsync(new[] { "http://google.com", "http://microsoft.com", "http://yahoo.com" })
.Start();
}
private Task ProcessUrlsAsync(string[] urls)
{
return new Task(() =>
{
foreach (string url in urls)
{
TaskAwaiter<string> awaiter = ProcessUrlAsync(url);
// or the next line, in case we use method *
// TaskAwaiter<string> awaiter = ProcessUrlAsync(url).GetAwaiter();
string result = awaiter.GetResult();
MessageBox.Show(result);
}
});
}
// Awaiter inside
private TaskAwaiter<string> ProcessUrlAsync(string url)
{
TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
{
// TODO: put custom processing of document right here
taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
});
webBrowser1.DocumentCompleted += handler;
taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });
webBrowser1.Navigate(url);
return taskCompletionSource.Task.GetAwaiter();
}
// (*) Task<string> instead of Awaiter
//private Task<string> ProcessUrlAsync(string url)
//{
// TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
// var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
// {
// taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
// });
// webBrowser1.DocumentCompleted += handler;
// taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });
// webBrowser1.Navigate(url);
// return taskCompletionSource.Task;
//}
And the next sample contains the sample implementation of awaiter struct Eric Lippert was talking about here.
public partial class Form1 : Form
{
public struct WebBrowserAwaiter
{
private readonly WebBrowser _webBrowser;
private readonly string _url;
private readonly TaskAwaiter<string> _innerAwaiter;
public bool IsCompleted
{
get
{
return _innerAwaiter.IsCompleted;
}
}
public WebBrowserAwaiter(WebBrowser webBrowser, string url)
{
_url = url;
_webBrowser = webBrowser;
_innerAwaiter = ProcessUrlAwaitable(_webBrowser, url);
}
public string GetResult()
{
return _innerAwaiter.GetResult();
}
public void OnCompleted(Action continuation)
{
_innerAwaiter.OnCompleted(continuation);
}
private TaskAwaiter<string> ProcessUrlAwaitable(WebBrowser webBrowser, string url)
{
TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
{
// TODO: put custom processing of document here
taskCompletionSource.SetResult(e.Url + ": " + webBrowser.Document.Title);
});
webBrowser.DocumentCompleted += handler;
taskCompletionSource.Task.ContinueWith(s => { webBrowser.DocumentCompleted -= handler; });
webBrowser.Navigate(url);
return taskCompletionSource.Task.GetAwaiter();
}
}
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ProcessUrlsAsync(new[] { "http://google.com", "http://microsoft.com", "http://yahoo.com" })
.Start();
}
private Task ProcessUrlsAsync(string[] urls)
{
return new Task(() =>
{
foreach (string url in urls)
{
var awaiter = new WebBrowserAwaiter(webBrowser1, url);
string result = awaiter.GetResult();
MessageBox.Show(result);
}
});
}
}
}
Hope this helps.
Instead of using wbCourseOverview_Navigated use webBrowser1_DocumentCompleted when fist URL load completed done your job and go to next url
List<string> urls = new List<string>();
int count = 0;
public Form1()
{
InitializeComponent();
webBrowser1.DocumentCompleted+=new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
}
private void Form1_Load(object sender, EventArgs e)
{
webBrowser1.Navigate(urls[count++]);
}
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//Do something
webBrowser1.Navigate(urls[count++]);
}

Set TextBlock from Async Completion Handler

I would like to set the text on the MainPage of my app, based on the response of an Async call to Web Service.
Im getting a "The application called an interface that was marshalled for a different thread". So I know that I need to execute the
MainPage.TB_Response.text = response;
On the Primary/Main Thread, but I am unsure as to how i would go about this
Edit: Here is my Response Handler
private void ReadResponse(IAsyncResult asyncResult)
{
System.Diagnostics.Debug.WriteLine("ReadResponse");
try
{
// The downloaded resource ends up in the variable named content.
var content = new MemoryStream();
// State of request is asynchronous.
//RequestState myRequestState = (RequestState)asyncResult.AsyncState;
HttpWebRequest myHttpWebRequest2 = (HttpWebRequest)asyncResult.AsyncState;
HttpWebResponse response = (HttpWebResponse)myHttpWebRequest2.EndGetResponse(asyncResult);
//do whatever
using (Stream responseStream = response.GetResponseStream())
{
responseStream.CopyTo(content);
byte[] data = content.ToArray();
if (data.Length > 0)
{
string temp = System.Text.Encoding.UTF8.GetString(data, 0, data.Length);
MainPage.TB_Reponse.Text = temp;
System.Diagnostics.Debug.WriteLine(temp);
}
}
}
catch (WebException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
Edit2: My MainPage Class
public sealed partial class MainPage : Page
{
public static TextBlock TB_Reponse;
public MainPage()
{
this.InitializeComponent();
MainPage.TB_Reponse = this.TB_Response;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
private void BTN_Login_Click(object sender, RoutedEventArgs e)
{
...
}
}
So I found this solution to solve my problem
Delegate 'System.Action<object>' does not take 0 arguments - C# - Task
I Setup my ServerRequest object to store the current synchronisation context
ui = TaskScheduler.FromCurrentSynchronizationContext();
then in my Response Handler I run the following:
Task.Factory.StartNew(() =>
{
MainPage.TB_Reponse.Text = temp; //This is run on the same thread as the UI
},System.Threading.CancellationToken.None , TaskCreationOptions.None, instance.ui);

Categories