I need some help with handling Tasks. I have an XML String which is deserialized into a class. The class itself contains a property, e.g. rssProvider which is set to an unique value like MSN or YAHOO. However there can be multiple values delimited with an , in this field.
I am using this deserialized class instance in multiple Tasks. However the function which gets called in this task can only work with one rssProvider value, so I have a split on that string and a foreach loop which creates the task.
In the task itself the function I call needs the full object but with one rssProvider. However when I change the value of that property to the value in the foreach the other tasks will fail as they will get the same single value from the task which runs first.
Any ideas how I should restructure the logic? Thanks!
My code:
List<Task<ResponseOBJ>> tasks = new List<Task<ResponseOBJ>>();
// get List of rssProviders
string[] providers = request.requestBody.rssProvider.Split(',');
//go through each provider
foreach (string provider in providers)
{
Task<ResponseOBJ> task = Task.Factory.StartNew<ResponseOBJ>(() =>
{
request.requestBody.rssProvider = provider;
doStuff(request);
}
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
I would create a copy constructor in your Request object which copies the content of the original Request and creates a fresh one:
public class Request
{
public Request(Request oldRequest)
{
// initalize new request from the old
}
}
And then change my code to create a new request per task:
List<Task<ResponseOBJ>> tasks = new List<Task<ResponseOBJ>>();
// get List of rssProviders
string[] providers = request.requestBody.rssProvider.Split(',');
//go through each provider
foreach (string provider in providers)
{
Task<ResponseOBJ> task = Task.Factory.StartNew<ResponseOBJ>(() =>
{
request.requestBody.rssProvider = provider;
var newRequest = new Request(request);
doStuff(newRequest);
}
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
One option might be to change doStuff(request) to doStuff(request, provider) and remove the line request.requestBody.rssProvider = provider;, and then change your doStuff accordingly.
foreach (string provider in providers)
{
Task<ResponseOBJ> task = Task.Factory.StartNew<ResponseOBJ>(() =>
{
doStuff(request, provider);
}
tasks.Add(task);
}
Another option (as also mentioned in the above comments) is to create a new request object for each provider.
Related
I have an API POST endpoint creating a resource, that resource may have multiple relationships. To make sure the resource is created with valid relationships first I need to check if the given IDs exist. There are multiple such relations, and I don't want to await each sequentially. Here's my code:
[HttpPost]
public async Task<ActionResult<Person>> PostPerson(Person person)
{
ValueTask<Person> master, apprentice;
ValueTask<Planet> planet;
ValueTask<Models.LifeFormType> lifeFormType;
if (person.MasterId.HasValue)
{
master = _context.People.FindAsync(person.MasterId);
}
if (person.ApprenticeId.HasValue)
{
apprentice = _context.People.FindAsync(person.ApprenticeId);
}
if (person.FromPlanetId.HasValue)
{
planet = _context.Planets.FindAsync(person.FromPlanetId);
}
if (person.LFTypeId.HasValue)
{
lifeFormType = _context.LifeFormTypes.FindAsync(person.LFTypeId);
}
List<ValueTask> tasks = new List<ValueTask> {master, apprentice, planet, lifeFormType};
// if the above worked I'd process the tasks as they completed and throw errors
// if the given id was not found and such
_context.Attach(person);
// _context.People.Add(person);
await _context.SaveChangesAsync();
return CreatedAtAction("GetPerson", new { id = person.Id }, person);
}
As shown here I want to await the list of [master,apprentice,planet,lifeFormType] as they complete, but I get an error during the creation of the list that Local variable 'master' might not be initialized before accessing. So I tried in each check if the resource has that value to create an else block and somehow add a ValueTask.CompletedTask like so:
if (person.MasterId.HasValue)
{
master = _context.People.FindAsync(person.MasterId);
}
else
{
master = ValueTask.CompletedTask;
}
but then I get an error saying that Cannot convert source type 'System.Threading.Tasks.ValueTask' to target type 'System.Threading.Tasks.ValueTask<Models.Person>'.
How to do this? I guess I'll just await each and every request for now.
You can avoid this by initializing master at the declaration site.
The easiest way is using the default keyword.
ValueTask<Person> master = default;
I'm trying to list all items under a path for a determined bucket using Google Cloud Storage APIs. Under this path, there are more than 1000 items - which is the maximum number of items that ListObjects / ListObjectsAsync return.
In order to be able to repeat the call and get the next 1000 items, I need the NextPageToken from the previous response, that's how result pagination gets done! It's easy and reasonable.
However, I never get a NextPageToken within the responses. Am I missing something? This is my code so far. Some key points:
Althought there are more than +1000 items sharing the same prefix, I only get one response while looping through them in the enumerator.
The NextPageToken in the Console.WriteLine statement is always null.
Repeating the same call returns the same first 1000 objects over and over.
async IAsyncEnumerable<string> ListObjectsAsync(
string prefix, [EnumeratorCancellation] CancellationToken cancelToken)
{
var listObjectsOptions = new ListObjectsOptions
{
Fields = "items(name)"
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
Console.WriteLine(enumerator.Current.NextPageToken);
}
}
Thank you.
Apparently, I'm missing the nextPageToken field in the ListObjectsOptions used to make the request. Without specifying that field, the service won't return it - because of who knows why!
This code should work:
async IAsyncEnumerable<string> ListObjectsAsync(
string prefix, [EnumeratorCancellation] CancellationToken cancelToken)
{
var listObjectsOptions = new ListObjectsOptions
{
Fields = "items(name),nextPageToken"
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
Console.WriteLine(enumerator.Current.NextPageToken);
}
}
The nice thing is that you don't even have to use the NextPageToken explicitly. This is, you don't have to do something like this:
string token = null;
do
{
var options = new ListObjectsOptions
{
Fields = "items(name),nextPageToken",
PageToen = token
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
token = enumerator.Current.NextPageToken;
}
}
while (!string.IsNullOrEmpty(token));
...because the enumerator you got in rawResponses.GetEnumerator() will take care of using the token of a response to automatically fetch the next (if needed) while iterating them.
So the first piece of code is valid to iterate over +1000 objects in a single call.
I'm looking for a real simple example of how to add an object to cache, get it back out again, and remove it.
The second answer here is the kind of example I'd love to see...
List<object> list = new List<Object>();
Cache["ObjectList"] = list; // add
list = ( List<object>) Cache["ObjectList"]; // retrieve
Cache.Remove("ObjectList"); // remove
But when I try this, on the first line I get:
'Cache' is a type, which is not valid in the given context.
And on the third line I get:
An object method is required for the non-static field blah blah blah
So, let's say I have a List<T>...
var myList = GetListFromDB()
And now I just wanna add myList to the cache, get it back out, and remove it.
.NET provides a few Cache classes
System.Web.Caching.Cache - default caching mechanizm in ASP.NET. You can get instance of this class via property Controller.HttpContext.Cache also you can get it via singleton HttpContext.Current.Cache. This class is not expected to be created explicitly because under the hood it uses another caching engine that is assigned internally.
To make your code work the simplest way is to do the following:
public class AccountController : System.Web.Mvc.Controller{
public System.Web.Mvc.ActionResult Index(){
List<object> list = new List<Object>();
HttpContext.Cache["ObjectList"] = list; // add
list = (List<object>)HttpContext.Cache["ObjectList"]; // retrieve
HttpContext.Cache.Remove("ObjectList"); // remove
return new System.Web.Mvc.EmptyResult();
}
}
System.Runtime.Caching.MemoryCache - this class can be constructed in user code. It has the different interface and more features like update\remove callbacks, regions, monitors etc. To use it you need to import library System.Runtime.Caching. It can be also used in ASP.net application, but you will have to manage its lifetime by yourself.
var cache = new System.Runtime.Caching.MemoryCache("MyTestCache");
cache["ObjectList"] = list; // add
list = (List<object>)cache["ObjectList"]; // retrieve
cache.Remove("ObjectList"); // remove
Here is the way that I've done it in the past:
private static string _key = "foo";
private static readonly MemoryCache _cache = MemoryCache.Default;
//Store Stuff in the cache
public static void StoreItemsInCache()
{
List<string> itemsToAdd = new List<string>();
//Do what you need to do here. Database Interaction, Serialization,etc.
var cacheItemPolicy = new CacheItemPolicy()
{
//Set your Cache expiration.
AbsoluteExpiration = DateTime.Now.AddDays(1)
};
//remember to use the above created object as third parameter.
_cache.Add(_key, itemsToAdd, cacheItemPolicy);
}
//Get stuff from the cache
public static List<string> GetItemsFromCache()
{
if (!_cache.Contains(_key))
StoreItemsInCache();
return _cache.Get(_key) as List<string>;
}
//Remove stuff from the cache. If no key supplied, all data will be erased.
public static void RemoveItemsFromCache(_key)
{
if (string.IsNullOrEmpty(_key))
{
_cache.Dispose();
}
else
{
_cache.Remove(_key);
}
}
EDIT: Formatting.
BTW, you can do this with anything. I used this in conjunction with serialization to store and retrieve a 150K item List of objects.
If your using MemoryCache here is a very simple example:
var cache = MemoryCache.Default;
var key = "myKey";
var value = "my value";
var policy = new CacheItemPolicy { SlidingExpiration = new TimeSpan(2, 0, 0) };
cache.Add(key, value, policy);
Console.Write(cache[key]);
I wrote LazyCache to make this as simple and painless as possible, while also making sure you only execute your cacheable function calls once, even if two threads try and cache them at the same time.
Run the following command in the Package Manager Console
PM> Install-Package LazyCache
Add the namespace at the top of you class
using LazyCache;
and now cache stuff:
// Create the cache - (in constructor or using dependency injection)
IAppCache cache = new CachingService();
// Get products from the cache, or if they are not
// cached then get from db and cache them, in one line
var products = cache.GetOrAdd("get-products", () => dbContext.Products.ToList());
// later if you want to remove them
cache.Remove("get-products");
See more on the cache aside pattern or in the the LazyCache docs
Try this third party cache: CacheCrow, it is a simple LFU based cache.
Install using powershell command in visual studio: Install-Package CacheCrow
Code Snippet:
// initialization of singleton class
ICacheCrow<string, string> cache = CacheCrow<string, string>.Initialize(1000);
// adding value to cache
cache.Add("#12","Jack");
// searching value in cache
var flag = cache.LookUp("#12");
if(flag)
{
Console.WriteLine("Found");
}
// removing value
var value = cache.Remove("#12");
For more information you can visit: https://github.com/RishabKumar/CacheCrow
The code below iterates through a List collection. The Server object simply contains a description and URL property and is populated at the start of program execution.
For each Server in List<Server> I start a new task that calls a function called GetMonitorData. GetMonitorData simply reads the remote file in, parses out some data, and returns a ListViewItem containing the parsed out data. Finally, after the task finishes I add the returned ListViewItem to the ListView in ContinueWith():
foreach (Server server in Servers)
{
Task<ListViewItem> mainTask = Task.Factory.StartNew<ListViewItem>(() =>
{
return GetMonitorData(server);
});
Task contTask = mainTask.ContinueWith(task =>
{
lstServers.Items.Add(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
//System.Threading.Thread.Sleep(500);
}
private ListViewItem GetMonitorData(Server server)
{
XDocument monitorDoc = XDocument.Load(server.MonitorURL);
string version = monitorDoc.Root.Element("version").Value;
ListViewItem lstItem = new ListViewItem(new string[] { server.ClientName, version });
return lstItem;
}
public class Server
{
public string ClientName { get; set; }
public string MonitorURL { get; set; }
}
For some reason though I just keep getting the same ListViewItem back in ContinueWith(), so my ListView has multiple copies of the same ListViewItem in it. Interestingly, if I uncomment the System.Threading.Thread.Sleep(500) line (thereby giving the task a little time to finish) I get a unique ListViewItem for each Server in List<Server> as expected.
What am I missing?
I found a solution that works! Before creating the task I assign the parameter I pass to GetMonitorData() to a temporary variable first. See modified code below:
foreach (Server server in Servers)
{
Server tmpServer = server;
Task<ListViewItem> mainTask = Task.Factory.StartNew<ListViewItem>(() =>
{
return GetMonitorData(tmpServer);
});
Task contTask = mainTask.ContinueWith(task =>
{
lstServers.Items.Add(task.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
I didn't figure this out on my own though, this link provided the vital clue.
I am trying to follow simple Aditi Scheduler tutorial but I am getting error. Here is my code.
What am I doing wrong?
Error: The input is not a valid Base-64 string as it contains a
non-base 64 character, more than two padding characters, or an illegal
character among the padding characters.
[TestMethod]
public void ScheduledSMS()
{
var tenantId = "xxxxxxxxxxxmyid";
var secretKey = "xxxxxxxxxxxmykey";
var scheduledTasks = new ScheduledTasks(tenantId, secretKey);
// create a task
var task = new TaskModel
{
Name = "My first Scheduler job",
JobType = JobType.Webhook,
// use predefined CommonCronExpressions or build your own CRON expressions here http://cronmaker.com/
CronExpression = CommonCronExpressions.EveryMinute,
// use builders to set job properties for webhooks and azure queue
Params = ParamBuilderFactory
.WebHookBuilder("http://localhost:1901/SMS/SendText")
.Build()
};
var operationId = scheduledTasks.CreateTask(task); <------ Error happens here..
// all operations in the api follow fire and forget approach, once an operation like create/update/delete
// is requested it returns an operationId(Guid) which can be used to fetch the operation status
// operation status can be fetched in two ways:
// method 1: (without polling) returns the status without polling
var operationStatus = scheduledTasks.GetOperationStatus(operationId);
// method 2: (with polling) polls until the operation status changes to success/error or a timeout occurs
// var operationStatus = scheduledTasks.GetOperationStatus(operationId, true);
// get the task
TaskModel newTask = null;
if (operationStatus.Status == StatusCode.Success)
{
dynamic resultData = operationStatus.Data;
var newTaskId = resultData["Id"];
newTask = scheduledTasks.GetTask(Guid.Parse(newTaskId));
}
}
Maybe the problem is with the "localhost" in the url you pass to the WebHookBuilder?
This is a couple months old, but I had this same problem until I realized I had the tenantId and secretKey reversed (stupid, but easy mistake). Once I swapped them it worked fine.