Reputation: 10537
I have a list that I need to load with product names like:
List<string> prodNames= new List<string>();
Product data does not exist on my system. So, I must download them first.
Product data is downloaded one-by-one to local file system. Downloading a product fires an event handler which I handle and the handler provides the product name I need to add to the list.
I know number of products to get this way at all times, say 5.
I am trying to figure out how do I know when all 5 product names have been added to my list so I can proceed to display them. Here is the pseudocode
List ids = // list of product ids
List<string> prodNames= new List<string>(); //list to fill with prod names once they are downloaded
this.EventHandler += OnDownloadedProduct; //event fired once each product is downloaded
void GetProducts()
{
foreach (var id in ids)
{
// NOTE that I cannot modify neither downloader object nor GetProduct()
// method. These are not my code but come from a component for which
// I have no code access.
downloader.GetProduct(id); //fires OnDownloadedProduct once product is downloaded
}
}
//this event is fired once each product data is downloaded.
// ProductDataArgs is provided to me and I cannot modify it.
void OnDownloadedProduct(object sender, ProductDataArgs e)
{
// this is where I get product name and add it to the list
string prodName = e.getProdName();
prodNames.Add(prodName);
// <-- I could show prodName here in a message box but that
// would show one message box for all 5 products. I don't
// want that. I want to show all 5 product names in one
// message box.
}
I need to display all 5 product names in a form or message box. I could do it in above event but for each product. This would show 5 message boxes (or forms). But I need to show them all 5 in a single message box. So, I need to so something like this in caller:
List<string> prodNames = await.GetProducts(ids);
var names = null;
foreach(var prod in prodNames)
{
names += string.Format("{0}\n");
}
MesageBox.Show(names);
PROBLEM: The problem I face is that the operation may take time and that I dont know when the list is loaded with all 5 product names (because names are not available in GetProducts method but in OnDownloadedProduct event).
After some research on my own, Tasks/async/await seem to offer solution for this but all examples I have seen so far are not addressing the situation I described above where I depend on an event handler to get what I need (in this case, all 5 product names).
UPDATE Thanks to @Kris comment below, I have been able to rewrite my logic into this but it is not working properly:
public class ProductRetriver
{
List<int> ids = new List<int>(); // product ids
List<string> prodNames = new List<string>(); // product names
private TaskCompletionSource<List<string>> prodsTask;
this.EventHandler += OnDownloadedProduct;
Task<List<string>> async GetProducts(List<int> _ids)
{
ids = _ids;
prodsTask = new TaskCompletionSource<List<string>>();
foreach (var id in ids)
{
//PROBLEM: GetProduct() is not awaited, so method will finish
//before list is filled. Also, it seem to add duplicates for
//some reason to the list. I cannot modify the method as it is
//not code available to me.
downloader.GetProduct(id); //fires OnDownloadedProduct
}
return prodNames;
}
//this event is fired once each product data is downloaded
void OnDownloadedProduct(object sender, ProductDataArgs e)
{
// this is where I get product name and add it to the list
string prodName = e.getProdName();
prodNames.Add(prodName);
if (prodNames.Count == ids.Count)
{
prodsTask.SetResult(prodNames);
}
}
}
Upvotes: 0
Views: 440
Reputation: 1100
If I right understand, you don't need received result from downloader.GetProduct(id) ? If so - for what you want awaiting ? Just create list of tasks and wait while them all finished like this:
public class ProductRetriver
{
List<int> ids = new List<int>(); // product ids
List<string> prodNames = new List<string>(); // product names
private TaskCompletionSource<List<string>> prodsTask;
this.EventHandler += OnDownloadedProduct;
public static List<Task> TaskList = new List<Task>();
Task<List<string>> async GetProducts(List<int> _ids)
{
ids = _ids;
prodsTask = new TaskCompletionSource<List<string>>();
foreach (var id in ids)
{
TaskList.Add(downloader.GetProduct(id));
}
Task.WaitAll(TaskList.ToArray());
return prodNames;
}
//this event is fired once each product data is downloaded
void OnDownloadedProduct(object sender, ProductDataArgs e)
{
// this is where I get product name and add it to the list
string prodName = e.getProdName();
prodNames.Add(prodName);
if (prodNames.Count == ids.Count)
{
prodsTask.SetResult(prodNames);
}
}
}
Upvotes: 0
Reputation: 1985
i don't know what is this downloader and where it comes from, if eventhandler is out side downloader try this
public class ProductRetriver
{
List<int> ids = new List<int>();
List<string> prodNames = new List<string>(); //list to fill with prod names once they are downloaded
private TaskCompletionSource<List<string>> prodsTask;
this.EventHandler += OnDownloadedProduct; //event fired once each product is downloaded
Task<List<string>> async GetProducts(List<int> _ids)
{
ids = _ids;
prodsTask = new TaskCompletionSource<List<string>>();
foreach (var id in ids)
{
downloader.GetProduct(id); //fires OnDownloadedProduct once product is downloaded
}
}
//this event is fired once each product data is downloaded
void OnDownloadedProduct(object sender, ProductDataArgs e)
{
// this is where I get product name and add it to the list
string prodName = e.getProdName();
prodNames.Add(prodName);
if (prodNames.Count == ids.Count)
{
prodsTask.SetResult(prodNames);
}
}
}
if eventhandler is a part of downloader you can do this
Task<List<string>> async GetProducts(List<int> ids)
{
List<string> prodNames = new List<string>();
downloder.EventHandler += (s, e) => {
string prodName = e.getProdName();
prodNames.Add(prodName);
if (prodNames.Count == ids.Count)
{
prodsTask.SetResult(prodNames);
}
};
foreach (var id in ids)
{
downloader.GetProduct(id); //fires OnDownloadedProduct once product is downloaded
}
return prodNames;
}
you can call this like this
Products = await getproducts(ids);
Upvotes: 1