Set TextBlock from Async Completion Handler - c#

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);

Related

How to avoid Deadlock when using singleton Http Client in Winforms application

I have a legacy Windows Forms application that I am working on, I made some changes to the http client, I wanted to make it a singleton so that it could be reused throughout the application. It seems to be causing a deadlock.
I am going to paste all the code that I believe is involved below:
This is the calling code where the UI gets frozen, it never unfreezes.
private async void lbGroup_SelectedIndexChanged_1(object sender, EventArgs e)
{
int groupId = this.lbGroup.SelectedIndex + 1;
await LoadStores(groupId);
//The code below freezes the application
this.lbStore.DataSource = _stores;
this.txtSearch.Enabled = true;
this.lbStore.Enabled = true;
}
This is the LoadStores Method where the httpClient is used:
private async Task LoadStores(int group)
{
try
{
HttpResponseMessage res = await _httpClient.GetAsync("api/GetStoresByGroup/" + group.ToString());
res.EnsureSuccessStatusCode();
if (res.IsSuccessStatusCode)
{
var serializedStores = await res.Content.ReadAsStringAsync();
_stores = JsonConvert.DeserializeObject<IEnumerable<Store>>(serializedStores).Select(s => s.StoreName).ToList();
res.Content.Dispose();
}
}
catch (Exception ex)
{
ErrorLogger.LogError("Installation", $"Error getting stores list: {ex.Message}");
}
}
This is the Http Singleton Class:
public static class HttpClientSingleton
{
private static readonly HttpClient _instance;
static HttpClientSingleton()
{
_instance = new HttpClient();
_instance.BaseAddress = new Uri("https://www.i-city.co.za/");
_instance.DefaultRequestHeaders.Accept.Clear();
_instance.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public static HttpClient Instance
{
get
{
return _instance;
}
}
}
This is the form constructor where the HttpClient gets initiliazed:
public partial class frmInstallationHelper : Form
{
private static string _configDir;
private static string _localConfigDir;
private static int _storeID;
private static Activation _activation;
private static HttpClient _httpClient = HttpClientSingleton.Instance;
private static IEnumerable<string> _stores;
private static IEnumerable<string> _franchisees;
private int _smsCounter;
If I wrap the http request in a using statement inside of the LoadStores method, the app runs fine, but I don't want to dispose of the http Client as that defeats the purpose of making it a singleton.
Update: Problem Found
After following #MongZhu's lead I replicated the program and confirmed that none of the above code was actually causing the deadlock. It was caused by another method that was triggered by the lbStore list Box onSelectChange event displayd below:
private void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
LoadFranchisees(store).Wait();
this.lbFranchisees.DataSource = _franchisees;
}
The way I solved the problem was by changing it to look as follows:
private async void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
await LoadFranchisees(store);
this.lbFranchisees.DataSource = _franchisees;
}
I was busy changing all the .wait() methods to async / await, and I must have forgotten this one.
The deadlock arises because you used Wait in a method which was triggered by an async opertaion. Unfortunately it was masked very good by the apparent hanging in the line of the initialization of the DataSource. But this initialization triggered the SelectedIndexChanged of the listbox which had the evil Wait call in it. Making this method async and await the result will evaporate the deadlock.
private async void lbStore_SelectedIndexChanged_1(object sender, EventArgs e)
{
string store = this.lbStore.GetItemText(this.lbStore.SelectedItem);
_franchisees = await LoadFranchisees(store);
this.lbFranchisees.DataSource = _franchisees;
}
I would suggest to return the stores directly from the method instead of using a class variable as transmitter. This way you would also avoid race conditions (to which methods that use class variables are very much prone) If you need it further you could store the returning value inside the _stores variable. But a loading method should rather return the results instead of secretely storing it somewhere hidden from the user of this method.
private async Task<List<Store>> LoadStores(int group)
{
try
{
HttpResponseMessage res = await _httpClient.GetAsync("api/GetStoresByGroup/" + group.ToString()))
res.EnsureSuccessStatusCode();
if (res.IsSuccessStatusCode)
{
var serializedStores = await res.Content.ReadAsStringAsync();
res.Content.Dispose();
return JsonConvert.DeserializeObject<IEnumerable<Store>>(serializedStores).Select(s => s.StoreName).ToList();
}
}
catch (Exception ex)
{
ErrorLogger.LogError("Installation", $"Error getting stores list: {ex.Message}");
}
}
You can await the result in the event:
private async void lbGroup_SelectedIndexChanged_1(object sender, EventArgs e)
{
int groupId = this.lbGroup.SelectedIndex + 1;
_stores = await LoadStores(groupId);
this.lbStore.DataSource = _stores;
this.txtSearch.Enabled = true;
this.lbStore.Enabled = true;
}
The same logic applies to the LoadFranchisees method, refactor it so that it returns the data. This makes your code much more understandable. Don't hide information from the reader of a method. It could be you in 6 Month trying to figure out what da heck you did there.... Be nice to your future self at least ;)

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 ;-)

How can I update my Window controls from an STA Thread when calling from an async operation?

My server application is a WPF project which uses asynchronous callbacks to handle client requests and to send responses back to the client.
The server is to update its database based on the data received from the client and, depending on the nature of that data, push an alert through its own UI to reflect the new alert.
When it comes time to update the UI, I'm getting an error saying that the calling thread must be an STA thread or some such thing.
How can I make sure that the thread that's trying to update the UI is properly set up to do it without causing errors?
See below for all of my code and commentry about what the code is doing.
Client Helper
The ClientHelper class is a wrapper for client requests.
public class ClientHelper
{
private readonly TcpClient _Client;
private readonly byte[] _Buffer;
public Client(TcpClient client)
{
_Client = client;
int BufferSize = _Client.ReceiveBufferSize;
_Buffer = new byte[BufferSize];
}
public TcpClient TcpClient
{
get { return _Client; }
}
public byte[] Buffer
{
get { return _Buffer; }
}
public NetworkStream NetworkStream
{
get { return TcpClient.GetStream(); }
}
}
FooServer
The server uses a TcpListener that's running on its own thread so as to avoid locking the UI.
public class FooServer
{
private TcpListener Svr;
public void StartServer()
{
Thread ListenerThread = new Thread(new ThreadStart(() =>
{
Svr = new TcpListener(IPAddress.Parse("127.0.0.1"), 13000);
Svr.Start();
Svr.BeginAcceptTcpClient(AcceptClientCallback, null);
}));
ListenerThread.SetApartmentState(ApartmentState.STA);
ListenerThread.IsBackground = true;
ListenerThread.Start();
}
The server keeps track of its connected clients by maintaining a list of them.
private List<Client> ConnectedClients = new List<Client>();
private void AcceptClientCallback(IAsyncResult result)
{
TcpClient Client;
try
{
Client = Svr.EndAcceptTcpClient(result);
}
catch (Exception ex)
{
OnError(Svr, ex);
//Svr.Stop();
return;
}
Svr.BeginAcceptTcpClient(AcceptClientCallback, null);
ClientHelper _Client = new ClientHelper(Client);
ConnectedClients.Add(_Client);
NetworkStream Stream = _Client.NetworkStream;
Stream.BeginRead(_Client.Buffer, 0, _Client.Buffer.Length, ReadCallback, _Client);
}
After it reads the client's data, the server executes functions that manipulate the data and forward the alert to the UI. HandleClientData is where all of this starts. Its the last read that the server does.
private void ReadCallback(IAsyncResult result)
{
ClientHelper Client = result.AsyncState as ClientHelper;
if (Client != null)
{
NetworkStream Stream = Client.NetworkStream;
int Read;
try
{
Read = Stream.EndRead(result);
}
catch (Exception ex)
{
OnError(Client, ex);
return;
}
if (Read == 0)
{
Client.TcpClient.Close();
ConnectedClients.Remove(Client);
return;
}
byte[] Data = new byte[Read];
Buffer.BlockCopy(Client.Buffer, 0, Data, 0, Read); // copy read data to the client's buffer
Stream.BeginRead(Client.Buffer, 0, Read, ReadCallback, Client); // read data
HandleClientData(Stream, Encoding.ASCII.GetString(Client.Buffer, 0, Data.Length));
}
}
private void HandleClientData(NetworkStream stream, string data)
{
byte[] value = null;
try
{
string[] Data = data.Split(',');
if (String.Equals(Data[0], "GetAllFoo"))
{
value = Encoding.ASCII.GetBytes(GetFoo());
}
else if (String.Equals(Data[0], "GetAFoo"))
{
int FooId;
Int32.TryParse(Data[1], out FooId);
value = Encoding.ASCII.GetBytes(GetFoo(FooId));
}
else
{
// Update the Foo in the database to reflect the latest state of every component.
UpdateFoo(Data);
// evaluate the data for a fault and raise an alert if there's something wrong.
if (!EvaluateFooData(Data[1]))
{
AddAlert();
}
value = Encoding.ASCII.GetBytes("SUCCESS,The Foo was successfully updated.|");
}
stream.Write(value, 0, value.Length);
}
catch (Exception ex)
{
string Error = String.Format("ERR,{0}", ex.Message);
byte[] ErrorBytes = Encoding.ASCII.GetBytes(Error);
stream.Write(ErrorBytes, 0, ErrorBytes.Length);
return;
}
}
}
EvaluateFooData checks the client data against acceptable norms and adds any deviation to a list that gets read by AddAlert below which adds the alerts to the database.
public void AddAlert()
{
ApplicationDbContext Context = new ApplicationDbContext();
foreach (Alert Alert in Alerts)
{
Context.Alerts.Add(Alert);
}
Context.SaveChanges();
OnRaiseAlert();
}
public event EventHandler RaiseAlert;
protected virtual void OnRaiseAlert()
{
RaiseAlert?.Invoke(this, null);
}
Using the EventHandler which is registered on the UI, the server pushes an alert to the UI:
public MainWindow()
{
InitializeComponent();
Server.RaiseAlert += Server_RaiseAlert;
}
private void Server_RaiseAlert(object sender, EventArgs e)
{
ApplicationDbContext Context = new ApplicationDbContext();
var Alerts = Context.Alerts.Where(x => x.IsResolved == false).ToList();
StackPanel FooStackPanel = new StackPanel();
spFoo.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { FooStackPanel = spFoo; }));
if (Alerts != null && Alerts.Count >= 1)
{
foreach (Alert Alert in Alerts)
{
Button Btn = (Button)FooStackPanel.Children[FooId];
Btn.Style = FindResource("FooAlertIcon") as Style;
}
}
}
Server_RaiseAlert updates the UI by changing the style of buttons that were created during initialization of the Window so that those buttons now indicate a problem with that Foo. The basic concept is green = good, red = bad.
Do everything that manipulates UI elements inside the Dispatcher Action:
private void Server_RaiseAlert(object sender, EventArgs e)
{
var context = new ApplicationDbContext();
var alerts = context.Alerts.Where(x => x.IsResolved == false).ToList();
if (alerts.Count > 0)
{
spFoo.Dispatcher.Invoke(new Action(() =>
{
foreach (var alert in alerts)
{
var button = (Button)spFoo.Children[FooId];
button.Style = FindResource("FooAlertIcon") as Style;
}
}));
}
}
Note however that from your question it isn't clear where FooId comes from.

Get Value from async Method?

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.

Categories