How to pass a data from new thread? - c#

I have a class:
class ShowComboBoxUpdater
{
private ComboBox _showComboBox;
private String _searchString;
private RequestState _endState;
public event EventHandler ResultUpdated;
public string[] getShowList()
{
if (_endState.serverQueryResult != null)
return _endState.serverQueryResult;
return new string[] { "" };
}
public ShowComboBoxUpdater(ComboBox combo, Image refreshImage)
{
_showComboBox = combo;
_refreshImage = refreshImage;
_endState = new RequestState();
}
public void RequestUpdatingComboSource()
{
_searchString = _showComboBox.Text;
Thread t = new Thread(new ThreadStart(MakeServerConnectionThread));
t.IsBackground = true;
t.Start();
}
private void MakeServerConnectionThread()
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://services.tvrage.com/myfeeds/search.php?show=" + _searchString);
_endState.request = request;
IAsyncResult result = request.BeginGetResponse(new AsyncCallback(RequestingThread), _endState);
ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, new WaitOrTimerCallback(ScanTimeoutCallback), _endState, (30 * 1000), true);
}
private void RequestingThread(IAsyncResult result)
{
RequestState state = (RequestState)result.AsyncState;
WebRequest request = (WebRequest)state.request;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
Stream bodyStream = response.GetResponseStream();
StreamReader r = new StreamReader(bodyStream);
string xmlResponse = r.ReadToEnd().Trim();
using (StringReader XMLStream = new StringReader(xmlResponse))
{
XPathNavigator feed = new XPathDocument(XMLStream).CreateNavigator();
XPathNodeIterator nodesNavigator = (XPathNodeIterator)feed.Evaluate("descendant::show/name/text()");
int titlesCount = nodesNavigator.Count;
string[] titles = new string[titlesCount];
foreach (XPathNavigator n in nodesNavigator)
{
titles[--titlesCount] = n.Value;
}
state.serverQueryResult = titles;
if (this.ResultUpdated != null) this.ResultUpdated(this, new EventArgs());
}
}
private static void ScanTimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
RequestState reqState = (RequestState)state;
if (reqState != null)
reqState.request.Abort();
}
}
}
In my main thread I create ShowComboBoxUpdater and connect event ResultUpdate to other event. Then I am calling RequestUpdatingComboSource() method. I have my event activated but, how can I get the resulting serverQueryResult ? I know it's there but everything that I try results in exception that what I want to get is "owned by other thread".

How to pass value ?
public class MyArgs : EventArgs
{
//Declare any specific type here
public string ResultToPass { get; private set; }
public MyArgs()
{
}
}
if (this.ResultUpdated != null) this.ResultUpdated(this, new MyArgs(){ResultToPass="Your actual result"} );
How to update result ?
Capture SynchronizationContext of the UI ( main thread) in order to instruct whenever you want the value to be updated back in the UI. Send/Post method on the captured SynchronizationContext reference in order to push the message into UI thread.
public partial class MainWindow : Window
{
SynchronizationContext UISyncContext;
public MainWindow()
{
InitializeComponent();
}
public StartProcessing()
{
//Let say this method is been called from UI thread. i.e on a button click
//capture the current synchronization context
UISyncContext=TaskScheduler.FromCurrentSynchronizationContext;
}
public UpdateResultInUI()
{
//Let's say this is is the method which user triggers at
//some point in time ( with the assumption that we have Myresult in hand)
if(UISyncContext!=null)
UISyncContext.Send(new SendOrPostCallback(delegate{ PutItInUI }),null);
//Use Send method - to send your request synchronously
//Use Post method- to send your request asynchronously
}
void PutItInUI()
{
//this method help you to put your result in UI/controls
}

Related

Why Task.Run not processing all lines on Task?

Im using WebApi to Deserialize Object on client side, witch contains some lightweight images, the code reads:
private void Button_Click(object sender, object e)
{
LoadApi();
}
private async void LoadApi()
{
using (var client = new HttpClient())
{
var responseMessage = await client.GetAsync("http://" +
TxtIP.Text + "/api/prod");
if (responseMessage.StatusCode == System.Net.HttpStatusCode.OK)
{
List<ClsProd> lstData = new List<ClsProd>();
var jsonResponse = await
responseMessage.Content.ReadAsStringAsync();
if (jsonResponse != null)
{
lstData = Newtonsoft.Json.JsonConvert.DeserializeObject<List<ClsProd>>(jsonResponse);
}
ListView1.ItemsSource = lstData;
}
}
}
my ClsProd looks witch get all data from Web Api is:
public class ClsProd : System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg { get; set; }
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
ClsImgBase64 CImg = new ClsImgBase64();
CImg.EvtResult += CImg_EvtResult;
CImg.Start(imax);
}
private void CImg_EvtResult(ImageSource e)
{
ImgPg = e;
NotifyPropertyChanged("ImgPg");
}
}
All data is properly fetch and displayed on list, including string SImax witch is image encoded as Base64 string. The only problem is image conversion from base64 string to image is not happening.
Here is my class it does not pass the 1st statment on Task.Run, please help me find what is wrong. Also same funcition works when called from async void.
public class ClsImgBase64
{
public event Action<ImageSource> EvtResult;
public ClsImgBase64()
{
}
public void Start(string s)
{
System.Threading.Tasks.Task.Run(async () =>
{
//read stream
byte[] bytes = Convert.FromBase64String(s);
var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
//decode image
//var decoder = await BitmapDecoder.CreateAsync(image);
image.Seek(0);
//create bitmap
var output = new WriteableBitmap(1, 1);
await output.SetSourceAsync(image);
if (EvtResult != null)
{
EvtResult(output);
}
});
}
}
As per async void there's probably an Exception thrown which was lost and not displayed bacause the executing code is not awaited. Let's fix it.
Web part
avoid async void in methods that's aren't event handlers, also handle all possible exceptions in async void method
HttpClient is intended to be instantiated once per app rather than per use
HttpResponseMessage is IDisposable
private async void Button_Click(object sender, object e)
{
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
// show ex.Message here in UI or log it
}
}
private static readonly HttpClient _client = new HttpClient();
private async Task LoadDataAsync()
{
using var response = await _client.GetAsync($"http://{TxtIP.Text}/api/prod");
string json = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
List<ClsProd> data = JsonConvert.DeserializeObject<List<ClsProd>>(json);
ListView1.ItemsSource = data;
await DecodeAllImagesAsync(data);
}
// decoding all at once asynchronously, see implementation below
private Task DecodeAllImagesAsync(List<ClsProd> data)
{
return Task.WhenAll(data.Select(item => item.DecodeImageAsync()).ToArray());
}
Consider using System.Text.Json to deserealize instead of old Newtonsoft.Json. It would allow to deserealize response.Content as Stream, faster with less memory consumption e.g:
using var stream = await response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync();
List<ClsProd> data = await JsonSerializer.DeserealizeAsync<List<ClsProd>>(stream);
Data part
Use using directives at the beggining of the code to attach namespaces that will help not to repeat namespaces in the code explicitly
using System.ComponentModel;
It makes possible to write INotifyPropertyChanged instead of System.ComponentModel.INotifyPropertyChanged. I'll remove inlined namespaces below.
don't start long-running job from a constructor, it's unpredictable behavior because costructor must be always successful. Start loading images later. Also constructor cannot await asynchronous tasks. Separate method can.
public class ClsProd : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ImageSource _imgPg;
public int IAuto { get; set; }
public int IDevc { get; set; }
public string SName { get; set; }
public string SImax { get; set; }
public ImageSource ImgPg
{
get => _imgPg;
set
{
_imgPg = value;
NotifyPropertyChanged();
}
}
public ClsProd(int auto, int devc, string name, string imax)
{
IAuto = auto;
IDevc = devc;
SName = name;
SImax = imax;
}
public async Task DecodeImageAsync()
{
ImgPg = await ClsImgBase64.DecodeAsync(SImax);
}
}
Decoder
As now it's awaitable and doesn't need a callback, decoding method doesn't interact with the instance data. So, it can be static.
public static class ClsImgBase64
{
public static async Task<ImageSource> DecodeAsync(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
using var stream = bytes.AsBuffer().AsStream().AsRandomAccessStream();
// stream.Seek(0); // not sure if it needed
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixelData = await decoder.GetPixelDataAsync();
var pixelArray = pixelData.DetachPixelData();
var bitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(pixelArray, 0, pixelArray.Length);
return bitmap;
}
}
Decoder's code based on this answer.
If it will be laggy, try to wrap 2 Decoder's lines with Task.Run. Only if it will be laggy.
using var stream = await Task.Run(() =>
{
byte[] bytes = Convert.FromBase64String(base64);
return bytes.AsBuffer().AsStream().AsRandomAccessStream();
});
Finally: give classes, methods and other things more clear names, that would make the code maintainable.

C# WebAPI Thread Aborting when Sharing Data Access Logic

I'm getting the following error on my C# Web API: "Exception thrown: 'System.Threading.ThreadAbortException' in System.Data.dll
Thread was being aborted". I have a long running process on one thread using my data access logic class to get and update records being process. Meanwhile a user submits another group to process which has need of the same data access logic class, thus resulting in the error. Here is a rough sketch of what I'm doing.
WebAPI Class:
public IHttpActionResult OkToProcess(string groupNameToProcess)
{
var logic = GetLogic();
//Gets All Unprocessed Records and Adds them to Blocking Queue
Task.Factory.StartNew(() => dataAccessLogic.LoadAndProcess(groupNameToProcess);
}
public IHttpActionResult AddToProcess(int recordIdToProcess)
{
StaticProcessingFactory.AddToQueue(recordIdToProcess);
}
StaticProcessingFactory
internal static ConcurrentDictionary<ApplicationEnvironment, Logic> correctors = new ConcurrentDictionary<ApplicationEnvironment, Logic>();
internal static BlockingCollection<CorrectionMessage> MessageQueue = new BlockingCollection<Message>(2000);
public void StartService(){
Task.Factory.StartNew(() => LoadService());
}
public void LoadService(){
var logic = GetLogic();
if(isFirstGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("FirstGroup");
if(isSeconddGroupOkToProcessAsPerTextFileLog())
logic.LoadAndProcess("SecondGroup");
}
public static GetLogic(){
var sqlConnectionFactory = Tools.GetSqlConnectionFactory();
string environment = ConfigurationManager.AppSettings["DefaultApplicationEnvironment"];
ApplicationEnvironment applicationEnvironment =
ApplicationEnvironmentExtensions.ToApplicationEnvironment(environment);
return correctors.GetOrAdd(applicationEnvironment, new Logic(sqlConnectionFactory ));
}
public static void AddToQueue(Message message, bool completeAdding = true)
{
if (MessageQueue.IsAddingCompleted)
MessageQueue = new BlockingCollection<Message>();
if (completeAdding && message.ProcessImmediately)
StartQueue(message);
else
MessageQueue.Add(message);
}
public static void StartQueue(Message message = null)
{
if (message != null)
{
if(!string.IsNullOrEmpty(message.ID))
MessageQueue.Add(message);
Logic logic = GetLogic(message.Environment);
try
{
var messages = MessageQueue.TakeWhile(x => logic.IsPartOfGroup(x.GroupName, message.GroupName));
if (messages.Count() > 0)
MessageQueue.CompleteAdding();
int i = 0;
foreach (var msg in messages)
{
i++;
Process(msg);
}
}
catch (InvalidOperationException) { MessageQueue.CompleteAdding(); }
}
}
public static void Process(Message message)
{
Var logic = GetLogic(message.Environment);
var record = logic.GetRecord(message.ID);
record.Status = Status.Processed;
logic.Save(record);
}
Logic Class
private readonly DataAccess DataAccess;
public Logic(SqlConnectionFactory factory)
{
DataAccess = new DataAcess(factory);
}
public void LoadAndProcess(string groupName)
{
var groups = DataAccess.GetGroups();
var records = DataAccess.GetRecordsReadyToProcess(groups);
for(int i = 0; i < records.Count; i++)
{
Message message = new Message();
message.Enviornment = environment.ToString();
message.ID = records[i].ID;
message.User = user;
message.Group = groupName;
message.ProcessImmediately = true;
StaticProcessingFactory.AddToQueue(message, i + 1 == records.Count);
}
}
Any ideas how I might ensure that all traffic from all threads have access to the Data Access Logic without threads being systematically aborted?

How to wait for methods to finish then do new action?

I'm setting up my architechture to use Cef.Offscreen. In order to make it easy to work with I have divided some parts. But I run into a problem that controller loading finshes and serves a view before everything has been able to load.
Here's my structure --> Controller
public ActionResult InitBrowser()
{
ICefSharpRenderer renderer = RendererSingelton.GetInstance();
//Try to render something in default appdomain
renderer.LoginToTradingView(null, null);
ViewBag.SiteTitle = BrowserActions.RunScriptInNamedBrowser("loginbrowser", #"(function() {return document.title;} )();");
ViewBag.ImagesixtyfourUrl = BrowserActions.TakeScreenshot("loginbrowser");
//this is returned to fast, we have to wait for all
return View();
}
I have this class to get do some basic actions and initialize if needed.
public class CefSharpRenderer : MarshalByRefObject, ICefSharpRenderer
{
private ChromiumWebBrowser _browser;
private TaskCompletionSource<JavascriptResponse> _taskCompletionSource;
private string _name;
public void LoginToTradingView(string url, string browserName)
{
CheckIfCefIsInitialized();
BrowserFactory.GetBrowserInstance(#"https://se.tradingview.com/", "loginbrowser");
}
public void CreateBrowserAndGoToUrl(string url, string browserName)
{
CheckIfCefIsInitialized();
BrowserFactory.GetBrowserInstance(url, "browserName");
}
public void CheckIfCefIsInitialized()
{
if (!Cef.IsInitialized)
{
var settings = new CefSettings();
var assemblyPath = Path.GetDirectoryName(new Uri(GetType().Assembly.CodeBase).LocalPath);
settings.BrowserSubprocessPath = Path.Combine(assemblyPath, "CefSharp.BrowserSubprocess.exe");
settings.ResourcesDirPath = assemblyPath;
settings.LocalesDirPath = Path.Combine(assemblyPath, "locales");
var osVersion = Environment.OSVersion;
//Disable GPU for Windows 7
if (osVersion.Version.Major == 6 && osVersion.Version.Minor == 1)
{
// Disable GPU in WPF and Offscreen examples until #1634 has been resolved
settings.CefCommandLineArgs.Add("disable-gpu", "1");
}
//Perform dependency check to make sure all relevant resources are in our output directory.
Cef.Initialize(settings, performDependencyCheck: false, cefApp: null);
}
}
}
I get my browserinstance here and connected the events to be fired.
public static class BrowserFactory
{
public static ChromiumWebBrowser GetBrowserInstance(string _url, string browsername)
{
if (!BrowserContainer.CheckIfBrowserExists(browsername))
{
ChromiumWebBrowser _browser = new ChromiumWebBrowser(_url);
_browser.LoadingStateChanged += BrowserEvents.OnLoadingStateChanged;
BrowserContainer.AddDataHolder(browsername, new DataBrowserHolder { BrowserName = browsername, ChromiumWebBrow = _browser });
return _browser;
}
return null;
}
}
Browserevent loads correct page.
public static class BrowserEvents
{
public static void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs args)
{
if (args.IsLoading == false)
{
ChromiumWebBrowser cwb = (ChromiumWebBrowser)sender;
if (cwb.Address == "https://se.tradingview.com/")
{
BrowserActions.LogInToTradingView("xxxxx", "yyyyyyy", "loginbrowser");
}
}
}
}
Last my browseractions, spare med for the thread sleeps it's just under construction and it works atm.
public static class BrowserActions
{
public static void LogInToTradingView(string twusername, string twpassword, string browserName)
{
ChromiumWebBrowser _dataholder = BrowserContainer.GetDataHolderByName(browserName).ChromiumWebBrow;
IFrame ifww = _dataholder.GetMainFrame();
// var lull = #"(function() { var serielength = TradingView.bottomWidgetBar._widgets.backtesting._reportWidgetsSet.reportWidget._data.filledOrders.length; return serielength; })();";
// JavascriptResponse _js = Task.Run(async () => { return await _browser.GetMainFrame().EvaluateScriptAsync(lull); }).Result;
ifww.ExecuteJavaScriptAsync(#"(function() { window.document.getElementsByClassName('tv-header__link tv-header__link--signin js-header__signin')[0].click();})();");
// var loginusernamescript =
var loginpasswordscript = #"(function() { window.document.getElementsByClassName('tv-control-material-input tv-signin-dialog__input tv-control-material-input__control')[1].value= " + twpassword + "; })();";
var clkloginbtn = #"(function() { document.getElementsByClassName('tv-button tv-button--no-border-radius tv-button--size_large tv-button--primary_ghost tv-button--loader')[0].click();})();";
Thread.Sleep(300);
ifww.ExecuteJavaScriptAsync(#"(function() { window.document.getElementsByClassName('tv-control-material-input tv-signin-dialog__input tv-control-material-input__control')[0].click();})();");
Thread.Sleep(50);
ifww.ExecuteJavaScriptAsync(#"(function() { window.document.getElementsByClassName('tv-control-material-input tv-signin-dialog__input tv-control-material-input__control')[0].value = '" + twusername + "';})();");
Thread.Sleep(50);
ifww.ExecuteJavaScriptAsync(#"(function() { window.document.getElementsByClassName('tv-control-material-input tv-signin-dialog__input tv-control-material-input__control')[1].click();})();");
Thread.Sleep(50);
ifww.ExecuteJavaScriptAsync(#"(function() { window.document.getElementsByClassName('tv-control-material-input tv-signin-dialog__input tv-control-material-input__control')[1].value = '" + twpassword + "';})();");
Thread.Sleep(50);
ifww.ExecuteJavaScriptAsync(#"(function() { document.getElementsByClassName('tv-button tv-button--no-border-radius tv-button--size_large tv-button--primary_ghost tv-button--loader')[0].click();})();");
}
public static string TakeScreenshot(string browserName)
{
try
{
Bitmap img = Task.Run(async () => { return await BrowserContainer.GetDataHolderByName(browserName).ChromiumWebBrow.ScreenshotAsync(); }).Result;
// object mgss = img.Clone();
string baseen = ExtraFunctions.ToBase64String(img, ImageFormat.Png);
return baseen;
}
catch (Exception e)
{
var x = e.InnerException;
return null;
}
}
public static string RunScriptInNamedBrowser(string browserName, string script)
{
try
{
string str = Task.Run(async () => { return await BrowserContainer.GetDataHolderByName(browserName).ChromiumWebBrow.GetMainFrame().EvaluateScriptAsync(script); }).Result.ToString();
// object mgss = img.Clone();
return str;
}
catch (Exception e)
{
var x = e.InnerException;
return null;
}
}
}
How can I get my browser actions to report back to my controller so that I can wait for them to finish?
For a Task asynchronous operation to report back, it's possible to use Progress<T>. How that's done is detailed in Enabling Progress and Cancellation in Async APIs. The key is:
var progressIndicator = new Progress<int>(ReportProgress);
This creates a Progress<T> object that can indicate how far a task is complete, and also call a custom method (ReportProgress) at set intervals. You can create a custom class if necessary instead of using int.
So your browser actions can report back to the controller with the progress reporting method until everything is complete.

REST in Unity (send messages from webserver to unity)

Is there a way to have a simple webserver send messages to Unity?
At the moment, we're doing a GET using UnityWebRequest.Get() in the update method. Here's the code:
// Update is called once per frame
void Update () {
StartCoroutine(GetData());
}
IEnumerator GetData()
{
UnityWebRequest uwr = UnityWebRequest.Get(url);
yield return uwr.Send();
if (uwr.isError)
{
Debug.Log(uwr.error);
}else
{
Debug.Log((float.Parse(uwr.downloadHandler.text) / 100));
fnumber = ((float.Parse(uwr.downloadHandler.text) / 100));
transform.position.Set(oldX, fnumber, oldZ);
}
}
However, this throws this error:
CANNOT RESOLVE DESTINATION HOST
I found this bug-report, which states, it would have been fixed, which doesn't seem so.
So, is there a way to have the server send messages to Unity?
Thanks
Expanding my comment to be more descriptive and concrete, my suggestion is to create a simple Thread that will serve as the communication manager with your server.
First of all you have to implement this class to call methods on Unity's UI thread.
When you implement this just create a class :
public class CommunicationManager
{
static readonly object syncRoot = new object();
static CommunicationManager _Instance;
public static CommunicationManager Instance
{
get
{
if ( _Instance == null )
{
lock ( syncRoot )
{
if ( _Instance == null )
{
_Instance = new CommunicationManager();
}
}
}
return _Instance;
}
}
volatile bool working = false;
Queue<Message> _messages;
private CommunicationManager()
{
_messages = new Queue<Message>();
InitializeCommunication();
}
void InitializeCommunication()
{
Thread t = new Thread(CommunicationLoop);
t.Start();
}
void CommunicationLoop()
{
Socket s = null;
try
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1337));
working = true;
}
catch(Exception ex)
{
working = false;
}
while ( working )
{
lock ( syncRoot )
{
while ( _messages.Count > 0 )
{
Message message = _messages.Dequeue();
MessageResult result = message.Process(s);
result.Notify();
}
}
Thread.Sleep(100);
}
}
public void EnqueueMessage(Message message)
{
lock ( syncRoot )
{
_messages.Enqueue( message );
}
}
}
Now you have to make Message and MessageResult objects :
public class Message
{
const string REQUEST_SCHEME = "{0} {1} HTTP/1.1\r\nHost: {hostname}\r\nContent-Length: 0\r\n\r\n";
string request;
Action<MessageResult> whenCompleted;
public Message(string requestUri, string requestType, Action<MessageResult> whenCompleted)
{
request = string.Format(REQUEST_SCHEME, requestType, requestUri);
this.whenCompleted = whenCompleted;
}
public MessageResult Process(Socket s)
{
IPEndPoint endPoint = (IPEndPoint)s.RemoteEndPoint;
IPAddress ipAddress = endPoint.Address;
request = request.Replace("{hostname}", ipAddress.ToString());
s.Send(Encoding.UTF8.GetBytes(request));
// receive header here which should look somewhat like this :
/*
HTTP/1.1 <status>
Date: <date>
<some additional info>
Accept-Ranges: bytes
Content-Length: <CONTENT_LENGTH>
<some additional info>
Content-Type: <content mime type>
*/
// when you receive this all that matters is <CONTENT_LENGTH>
int contentLength = <CONTENT_LENGTH>;
byte[] msgBuffer = new byte[contentLength];
if (s.Receive(msgBuffer) != contentLength )
{
return null;
}
return new MessageResult(msgBuffer, whenCompleted);
}
}
And lastly create MessageResult object :
public class MessageResult
{
Action<MessageResult> notifier;
byte[] messageBuffer;
public MessageResult(byte[] message, Action<MessageResult> notifier)
{
notifier = notifier;
messageBuffer = message;
}
public void Notify()
{
UnityThread.executeInUpdate(() =>
{
notifier(this);
});
}
}
This method will run aside from your main application so that wont produce any freezes and whatever.
To use this you can just call something like this :
Message message = new Message("/index.html", "GET", IndexDownloaded);
CommunicationManager.Instance.EnqueueMessage(message);
// ...
public void IndexDownloaded(MessageResult result)
{
// result available here
}
For more informations read this wikipedia article about HTTP.
This is simple way to get request from RESTful server :D.
private string m_URL = "https://jsonblob.com/api/d58d4507-15f7-11e7-a0ba-014f05ea0ed4";
IEnumerator Start () {
var webRequest = new WWW (m_URL);
yield return webRequest;
if (webRequest.error != null) {
Debug.Log (webRequest.error);
} else {
Debug.Log (webRequest.text);
}
}
Try to use the WWW call instead the UnityWebRequest which is broken.
What version of unity are you using ?

Async Functions making code not working

First, apologize me for the title... I haven't found any suiting my single case :P
I need to download an INI file to fill a Dictionary first of all. For this, I have this class:
public class Properties : INotifyPropertyChanged
{
private Dictionary<string, string> _properties;
public Properties()
{
_properties = new Dictionary<string, string>();
}
public async void Load(string uri)
{
Stream input = await connection(uri);
StreamReader rStream = new StreamReader(input);
string line;
while((line = rStream.ReadLine()) != null)
{
if(line != "")
{
int pos = line.IndexOf('=');
string key = line.Substring(0, pos);
string value = line.Substring(pos + 1);
_properties.Add(key, value);
Debug.WriteLine("Key: " + key + ", Value: " + value);
}
}
Debug.WriteLine("Properties dictionary filled with " + _properties.Count + " items.");
}
public async Task<Stream> connection(string uri)
{
var httpClient = new HttpClient();
Stream result = Stream.Null;
try
{
result = await httpClient.GetStreamAsync(uri);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.HResult);
}
return result;
}
public string getValue(string key)
{
string result = "";
try
{
result = _properties[key];
}
catch(Exception ex)
{
Debug.WriteLine(ex.Message);
Debug.WriteLine(ex.HResult);
result = "Not found";
}
return result;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
var Handler = PropertyChanged;
if (Handler != null)
Handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Mainly, the Dictionary contains a Key and a URL to download XML files to each page of the application.
The MainPage, which is the one that is gonna be fill has this code:
public MainPage()
{
this.InitializeComponent();
//Properties dictionary filling
prop = new Properties();
prop.Load("URL");
tab = new Bars.TopAppBar();
bab = new Bars.BottomAppBar();
tABar = this.topAppBar;
actvt = this.Activity;
bABar = this.bottomAppBar;
//Constructor of the UserControl
act = new Home(this, prop);
}
The constructor of the UserControl uses the MainPage as a Callback and the class Properties to look for the URL to download the XML file.
What happens is that, as Properties.Load() is an asynchronous method, is called, then the remaining lines are executed and when the program finishes, then goes back to Load() and fills the Dictionary.
As the Home constructor depends on a Value of Properties I get an Exception.
I have tried to create async voids assigning different priorities to force the program to run first one thing and then the remaining, but it hasn't worked either.
So, summarizing, I need to make sure that Properties is filled in the first place, anyone knows how to do it?
Thanks in advance!
Eva if you want to wait until the Load method is finished you have to change this method to return a Task.
public async Task LoadAsync(string uri)...
Better if you put your code in LoadedEventHandler of your page and make this method async. After that you will be able to await Properties.Load method.
If you want to call this method in constructor you can do it like this:
Task.Run(async () =>{
var task = p.LoadAsync().ConfigureAwait(false);
await task;
}).Wait()
But you have to be aware that deadlock can appear if in LoadAsync method will be await without disabled context switching (ConfigureAwait).

Categories